🔧(evals) add run_eval management command - #481
Conversation
|
Warning Review limit reached
Next review available in: 44 minutes Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available. How can I continue?After more reviews become available, a review can be triggered using the To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews. How do review limits work?CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability. For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window. Please refer docs for additional details. Review details⚙️ Run configurationConfiguration used: Repository UI Review profile: CHILL Plan: Pro Plus Run ID: ⛔ Files ignored due to path filters (1)
📒 Files selected for processing (54)
WalkthroughThis PR adds a behavioral evaluation framework with datasets, production-style agent wiring, run persistence, baseline comparison, dashboard generation, management commands, documentation, and tests. It also normalizes malformed Albert completion payloads before validation. ChangesBehavioral evals
Albert completion normalization
Estimated code review effort: 5 (Critical) | ~120 minutes Suggested reviewers: 🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✨ Finishing Touches 💡 1📝 Generate docstrings 💡
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
3baa473 to
2b2ad04
Compare
2b2ad04 to
41dd4c6
Compare
41dd4c6 to
1b29c2f
Compare
There was a problem hiding this comment.
Actionable comments posted: 1
♻️ Duplicate comments (2)
src/backend/chat/evals/README.md (2)
9-23:⚠️ Potential issue | 🟡 Minor | ⚡ Quick winAdd a language identifier to the fenced code block.
The code fence at line 9 still lacks a language identifier, triggering markdownlint MD040. Adding
textorplaintextwill resolve the linting warning.📝 Proposed fix
-``` +```text chat/evals/ ├── configs/ │ ├── __init__.py # REGISTRY — maps dataset name → EvalConfig🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/backend/chat/evals/README.md` around lines 9 - 23, The fenced code block in src/backend/chat/evals/README.md is missing a language identifier (triggering MD040); update the opening triple-backtick of the directory tree block to include a language token such as ```text or ```plaintext so the block becomes a proper fenced code block with a language identifier; locate the block showing "chat/evals/" and change its opening fence accordingly.
29-29:⚠️ Potential issue | 🟡 Minor | ⚡ Quick winClarify that URLs from user input are also allowed.
The description states URLs must come "only from tool output," but if the evaluator also accepts URLs present in
user_message(as flagged in previous review), this description is misleading. Consider updating to "only uses URLs from tool output or user input" to accurately reflect the evaluator behavior.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/backend/chat/evals/README.md` at line 29, Update the README description for the `url_hallucination` check to reflect that allowed URLs may come from both tool outputs and user input: change the phrase "only uses URLs from tool output" to "only uses URLs from tool output or user input" and ensure the `UrlRegexEvaluator` and `LLMJudge` references remain intact so readers know those components perform regex and semantic checks respectively (refer to `url_hallucination`, `UrlRegexEvaluator`, and `LLMJudge` in the same line).
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@src/backend/chat/management/commands/run_evals.py`:
- Around line 123-126: The _run_dataset function currently has a bool return
annotation and docstring but actually returns an EvaluationReport at the end
(see the return of EvaluationReport around the block that used to be line 145);
update the function signature and docstring of _run_dataset to reflect that it
returns an EvaluationReport (not bool), adjust the type hint to
EvaluationReport, and update the docstring to describe the EvaluationReport
contents so callers and type checks are correct.
---
Duplicate comments:
In `@src/backend/chat/evals/README.md`:
- Around line 9-23: The fenced code block in src/backend/chat/evals/README.md is
missing a language identifier (triggering MD040); update the opening
triple-backtick of the directory tree block to include a language token such as
```text or ```plaintext so the block becomes a proper fenced code block with a
language identifier; locate the block showing "chat/evals/" and change its
opening fence accordingly.
- Line 29: Update the README description for the `url_hallucination` check to
reflect that allowed URLs may come from both tool outputs and user input: change
the phrase "only uses URLs from tool output" to "only uses URLs from tool output
or user input" and ensure the `UrlRegexEvaluator` and `LLMJudge` references
remain intact so readers know those components perform regex and semantic checks
respectively (refer to `url_hallucination`, `UrlRegexEvaluator`, and `LLMJudge`
in the same line).
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Repository UI
Review profile: CHILL
Plan: Pro
Run ID: 0e639ce5-50e8-4a14-a9a1-c4f2d02fb4e4
📒 Files selected for processing (17)
CHANGELOG.mdMakefilesrc/backend/chat/evals/README.mdsrc/backend/chat/evals/__init__.pysrc/backend/chat/evals/configs/__init__.pysrc/backend/chat/evals/configs/base.pysrc/backend/chat/evals/configs/self_documentation.pysrc/backend/chat/evals/configs/url_hallucination.pysrc/backend/chat/evals/datasets/self_documentation.yamlsrc/backend/chat/evals/datasets/url_hallucination.yamlsrc/backend/chat/evals/evaluators/__init__.pysrc/backend/chat/evals/evaluators/url_regex.pysrc/backend/chat/management/__init__.pysrc/backend/chat/management/commands/__init__.pysrc/backend/chat/management/commands/run_evals.pysrc/backend/chat/providers/albert_models.pysrc/backend/chat/tests/agents/test_albert_models.py
✅ Files skipped from review due to trivial changes (1)
- CHANGELOG.md
🚧 Files skipped from review as they are similar to previous changes (10)
- src/backend/chat/evals/datasets/self_documentation.yaml
- Makefile
- src/backend/chat/evals/init.py
- src/backend/chat/evals/configs/url_hallucination.py
- src/backend/chat/evals/configs/init.py
- src/backend/chat/evals/configs/base.py
- src/backend/chat/providers/albert_models.py
- src/backend/chat/evals/evaluators/init.py
- src/backend/chat/tests/agents/test_albert_models.py
- src/backend/chat/evals/configs/self_documentation.py
1b29c2f to
4235b6a
Compare
This comment was marked as outdated.
This comment was marked as outdated.
4235b6a to
f736c54
Compare
There was a problem hiding this comment.
Actionable comments posted: 9
🧹 Nitpick comments (5)
src/backend/chat/evals/compare.py (2)
158-226: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winSplit report formatting into smaller render helpers.
SonarCloud reports
format_comparisonabove the allowed cognitive-complexity threshold. Extract dataset header rendering, case-change rendering, and score/reason rendering into helpers.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/backend/chat/evals/compare.py` around lines 158 - 226, The format_comparison function has too much nested rendering logic and exceeds the cognitive-complexity limit. Refactor it by extracting the dataset header/status lines, the case-change block, and the score/reason rendering into small helper functions near format_comparison in compare.py, then have format_comparison orchestrate those helpers while preserving the current output format.Source: Linters/SAST tools
76-143: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winSplit
compare_runsto satisfy the failing complexity gate.SonarCloud reports this function above the allowed cognitive-complexity threshold. Extracting dataset lookup/warning handling and per-case delta construction into helpers should unblock the check without changing behavior.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/backend/chat/evals/compare.py` around lines 76 - 143, The compare_runs function is exceeding the cognitive-complexity limit; split its dataset-level warning/lookup logic and case-delta building into small helpers. Refactor around compare_runs, _case_map, DatasetComparison, and CaseChange so the main function only orchestrates comparison while preserving the same behavior and warnings.Source: Linters/SAST tools
src/backend/chat/management/commands/run_evals.py (1)
20-21: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low valueRemove commented-out config lines.
These commented settings overrides are dead artifacts and flagged by static analysis.
🧹 Proposed fix
-# settings.LLM_DEFAULT_MODEL_HRID = "etalab-plateform-mistral-medium-2508" -# settings.LANGFUSE_ENABLED = False - -🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/backend/chat/management/commands/run_evals.py` around lines 20 - 21, Remove the commented-out settings overrides from run_evals.py; these are dead artifacts and should not remain in the command module. Delete the commented lines near the settings references in run_evals and keep the file focused on active configuration logic only.Source: Linters/SAST tools
src/backend/chat/tests/evals/test_production_agent.py (1)
83-90: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low valueTest depends on, and masks, the process-global caching behavior.
This test asserts identity-of-cached-state across two builder calls, which only passes because of the module-level globals in
production_agent.py. If those globals are reset for isolation (see the related comment onproduction_agent.py), this test would need both builds to run within the same un-reset scope. Worth making the dependency explicit (e.g., build both inside one logical step) so the test intent survives an isolation fix.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/backend/chat/tests/evals/test_production_agent.py` around lines 83 - 90, This test is relying on the process-global cache in build_production_agent_service, so make the dependency explicit by grouping the two service builds in one un-reset scope or shared step. Update test_eval_session_reuses_same_user_and_conversation to clearly exercise the same cached session behavior for both service_a and service_b, using the build_production_agent_service helper and the user/conversation assertions, so it still reflects the intended reuse if module-level globals in production_agent.py are later isolated or reset.src/backend/chat/evals/configs/faithfulness_rag.py (1)
82-86: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value
_CURRENT_CHUNKSis set but never reset between cases.The contextvar is overwritten on each
run_agentcall, so correctness relies on every case setting it before the stub reads it. This holds today (sequential,max_concurrency=1), but if a case ever hastool_output=None/empty while the tool still fires, it will silently fall back to the previous case's chunks only ifsetis skipped — hereset(None)is always called, so the fallback string handles it. No bug, but atry/finallyreset (orContextVar.resetvia the token) would make this robust against future concurrency changes.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/backend/chat/evals/configs/faithfulness_rag.py` around lines 82 - 86, The run_agent helper in faithfulness_rag.py sets _CURRENT_CHUNKS for each eval case but never restores the previous ContextVar state, so make it robust by capturing the token from _CURRENT_CHUNKS.set(...) and resetting it in a try/finally around agent.run. Update the run_agent function to use ContextVar.reset via the token so the stub state cannot leak across future concurrent or failed runs.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@src/backend/chat/evals/compare.py`:
- Around line 89-94: The comparison logic in compare_evals is treating missing
after-run coverage as only a warning, so incomplete candidate runs can still
pass when fail-on-regression is enabled. Update the dataset/case handling in
compare.py, especially the comparison path around the missing-dataset check and
the case-level logic, so missing items from the after run are recorded as
regression-like failures (or otherwise surfaced through the same failure path as
regressions). Make sure the Comparison object and compare_evals
--fail-on-regression flow use these coverage failures when deciding the final
exit status.
In `@src/backend/chat/evals/dashboard.py`:
- Line 39: The JSON string used for embedding dashboard payloads into the script
tag is currently taken directly from json.dumps(), which can still contain
script-breaking sequences like </script>. Update the payload
serialization/embedding path in the dashboard rendering code so the serialized
JSON is escaped for safe insertion into HTML script content before it is
interpolated and later parsed with JSON.parse(). Apply the same fix anywhere
else in this module that emits the same pattern.
- Around line 318-347: The saved eval values are being inserted into HTML
without escaping, so update the render paths that build `innerHTML` to sanitize
all run-artifact fields before interpolation. In `runParamsHtml` and the other
affected dashboard renderers, apply `escapeHtml()` consistently to values like
`run_id`, model/dataset/branch metadata, evaluator names, and LLM reasons, or
switch those fragments to DOM-based element creation before assignment. Use the
existing helpers such as `escapeHtml`, `runNote`, and the related HTML-building
functions in this module to ensure every saved field is treated as untrusted.
In `@src/backend/chat/evals/evaluators/url_regex.py`:
- Line 10: The URL detection regex in URL regex compilation is too strict about
the scheme and misses uppercase or mixed-case variants like HTTP://. Update the
pattern used by _URL_RE in the URL regex evaluator to match the scheme
case-insensitively, either by making the scheme portion explicitly flexible or
by using the appropriate regex flag, while keeping the rest of the URL-matching
behavior unchanged.
In `@src/backend/chat/evals/production_agent.py`:
- Around line 29-61: The eval-session caches in _get_eval_session_user() and
_get_eval_session_conversation() persist across the whole Python process, so
stale User and ChatConversation objects can survive between tests after
django_db rolls back the database rows. Add test isolation by resetting
EVAL_SESSION_USER and EVAL_SESSION_CONVERSATION to None between tests,
preferably with an autouse fixture, or clear them from
build_production_agent_service() when running in test mode. Keep the fix
localized to the eval-session globals in production_agent.py so each test
rebuilds fresh objects.
In `@src/backend/chat/evals/report_builder.py`:
- Around line 42-45: The `_repeat_passed` helper is using exact float equality
against 1.0, which can incorrectly fail near-perfect numeric evaluator scores.
Update the comparison in `_repeat_passed` to use an explicit pass threshold or
tolerance for non-boolean scores, while still preserving the intended behavior
for boolean-style assertions. Keep the logic localized to `_repeat_passed` so
the evaluator result handling remains easy to find and adjust.
In `@src/backend/chat/evals/storage.py`:
- Around line 94-98: The run ID generated by make_run_id only has second-level
timestamp precision, so repeated calls within the same second can collide and
cause save_run to overwrite earlier files or dedupe them in the index. Update
make_run_id to include finer-grained uniqueness, such as sub-second timestamp
precision or a short random suffix, while keeping the existing git_short/nogit
suffix behavior intact.
- Line 37: The exception handling in storage.py is using Python 2-style syntax,
which causes a SyntaxError and prevents the module from importing. Update the
except clauses in the affected error-handling blocks around the subprocess and
file lookup logic to use a tuple of exception types with Python 3 syntax, and
apply the same fix to both matching handlers so run_evals, save_run, and related
code can import successfully.
In `@src/backend/chat/tests/agents/test_albert_models.py`:
- Around line 245-250: The test for Albert model completion validation currently
only asserts choices normalization and does not verify that the response object
is coerced to chat.completion. Update
test_validate_completion_normalizes_non_standard_object to explicitly assert the
normalized object field on the result from _validate_completion, while keeping
the existing choices assertion, so the contract is locked in by the test.
---
Nitpick comments:
In `@src/backend/chat/evals/compare.py`:
- Around line 158-226: The format_comparison function has too much nested
rendering logic and exceeds the cognitive-complexity limit. Refactor it by
extracting the dataset header/status lines, the case-change block, and the
score/reason rendering into small helper functions near format_comparison in
compare.py, then have format_comparison orchestrate those helpers while
preserving the current output format.
- Around line 76-143: The compare_runs function is exceeding the
cognitive-complexity limit; split its dataset-level warning/lookup logic and
case-delta building into small helpers. Refactor around compare_runs, _case_map,
DatasetComparison, and CaseChange so the main function only orchestrates
comparison while preserving the same behavior and warnings.
In `@src/backend/chat/evals/configs/faithfulness_rag.py`:
- Around line 82-86: The run_agent helper in faithfulness_rag.py sets
_CURRENT_CHUNKS for each eval case but never restores the previous ContextVar
state, so make it robust by capturing the token from _CURRENT_CHUNKS.set(...)
and resetting it in a try/finally around agent.run. Update the run_agent
function to use ContextVar.reset via the token so the stub state cannot leak
across future concurrent or failed runs.
In `@src/backend/chat/management/commands/run_evals.py`:
- Around line 20-21: Remove the commented-out settings overrides from
run_evals.py; these are dead artifacts and should not remain in the command
module. Delete the commented lines near the settings references in run_evals and
keep the file focused on active configuration logic only.
In `@src/backend/chat/tests/evals/test_production_agent.py`:
- Around line 83-90: This test is relying on the process-global cache in
build_production_agent_service, so make the dependency explicit by grouping the
two service builds in one un-reset scope or shared step. Update
test_eval_session_reuses_same_user_and_conversation to clearly exercise the same
cached session behavior for both service_a and service_b, using the
build_production_agent_service helper and the user/conversation assertions, so
it still reflects the intended reuse if module-level globals in
production_agent.py are later isolated or reset.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Repository UI
Review profile: CHILL
Plan: Pro
Run ID: d5502ee0-230e-401b-a1cb-3575b2c3c3e2
📒 Files selected for processing (42)
.gitignoreCHANGELOG.mdMakefilesrc/backend/chat/evals/README.mdsrc/backend/chat/evals/__init__.pysrc/backend/chat/evals/baselines/.gitkeepsrc/backend/chat/evals/baselines/main.jsonsrc/backend/chat/evals/compare.pysrc/backend/chat/evals/configs/__init__.pysrc/backend/chat/evals/configs/base.pysrc/backend/chat/evals/configs/faithfulness_rag.pysrc/backend/chat/evals/configs/incertitude.pysrc/backend/chat/evals/configs/self_documentation.pysrc/backend/chat/evals/configs/url_hallucination.pysrc/backend/chat/evals/dashboard.pysrc/backend/chat/evals/dashboard/.gitkeepsrc/backend/chat/evals/dashboard/dashboard.htmlsrc/backend/chat/evals/datasets/faithfulness_rag.yamlsrc/backend/chat/evals/datasets/incertitude.yamlsrc/backend/chat/evals/datasets/self_documentation.yamlsrc/backend/chat/evals/datasets/url_hallucination.yamlsrc/backend/chat/evals/evaluators/__init__.pysrc/backend/chat/evals/evaluators/span.pysrc/backend/chat/evals/evaluators/url_regex.pysrc/backend/chat/evals/production_agent.pysrc/backend/chat/evals/report_builder.pysrc/backend/chat/evals/runs/2026-06-17T14-56-06Z_nogit.jsonsrc/backend/chat/evals/runs/2026-06-17T15-00-29Z_nogit.jsonsrc/backend/chat/evals/runs/2026-06-18T14-29-12Z_nogit.jsonsrc/backend/chat/evals/runs/2026-06-18T14-38-04Z_nogit.jsonsrc/backend/chat/evals/runs/index.jsonsrc/backend/chat/evals/storage.pysrc/backend/chat/evals/tool_output.pysrc/backend/chat/management/commands/compare_evals.pysrc/backend/chat/management/commands/create_eval_baseline.pysrc/backend/chat/management/commands/generate_eval_dashboard.pysrc/backend/chat/management/commands/run_evals.pysrc/backend/chat/providers/albert_models.pysrc/backend/chat/tests/agents/test_albert_models.pysrc/backend/chat/tests/evals/__init__.pysrc/backend/chat/tests/evals/test_production_agent.pysrc/backend/chat/tests/evals/test_storage.py
✅ Files skipped from review due to trivial changes (15)
- src/backend/chat/evals/tool_output.py
- src/backend/chat/management/commands/create_eval_baseline.py
- .gitignore
- src/backend/chat/evals/configs/incertitude.py
- src/backend/chat/management/commands/generate_eval_dashboard.py
- src/backend/chat/evals/runs/index.json
- src/backend/chat/management/commands/compare_evals.py
- src/backend/chat/evals/datasets/url_hallucination.yaml
- src/backend/chat/evals/runs/2026-06-17T15-00-29Z_nogit.json
- src/backend/chat/evals/runs/2026-06-18T14-29-12Z_nogit.json
- CHANGELOG.md
- src/backend/chat/evals/init.py
- src/backend/chat/evals/runs/2026-06-17T14-56-06Z_nogit.json
- src/backend/chat/evals/runs/2026-06-18T14-38-04Z_nogit.json
- src/backend/chat/evals/README.md
🚧 Files skipped from review as they are similar to previous changes (4)
- src/backend/chat/evals/configs/url_hallucination.py
- src/backend/chat/evals/evaluators/init.py
- src/backend/chat/providers/albert_models.py
- src/backend/chat/evals/datasets/self_documentation.yaml
f736c54 to
a698088
Compare
There was a problem hiding this comment.
🧹 Nitpick comments (1)
src/backend/chat/evals/compare.py (1)
112-210: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚖️ Poor tradeoffReduce cognitive complexity flagged by SonarCloud (
compare_runs28,format_comparison42).Both functions exceed the project's allowed cognitive complexity and are failing the SonarCloud gate. Consider extracting the missing-after-dataset branch (Lines 130-152) and the per-case diff (Lines 174-206) into helpers, and splitting
format_comparison's dataset/case rendering into smaller functions, to bring complexity under threshold without behavior change.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/backend/chat/evals/compare.py` around lines 112 - 210, The SonarCloud gate is failing because compare_runs and format_comparison are too complex. Refactor compare_runs by extracting the missing-after-dataset handling and the per-case comparison/diff logic into small helpers, using the existing compare_runs, _case_map, and _missing_after_case_change flow as anchors. Then split format_comparison into separate dataset-level and case-level rendering helpers so each function’s branching is reduced without changing behavior.Source: Linters/SAST tools
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Nitpick comments:
In `@src/backend/chat/evals/compare.py`:
- Around line 112-210: The SonarCloud gate is failing because compare_runs and
format_comparison are too complex. Refactor compare_runs by extracting the
missing-after-dataset handling and the per-case comparison/diff logic into small
helpers, using the existing compare_runs, _case_map, and
_missing_after_case_change flow as anchors. Then split format_comparison into
separate dataset-level and case-level rendering helpers so each function’s
branching is reduced without changing behavior.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Repository UI
Review profile: CHILL
Plan: Pro
Run ID: 473e3a31-0073-4a44-af70-dbc85465c519
📒 Files selected for processing (16)
Makefilesrc/backend/chat/evals/compare.pysrc/backend/chat/evals/configs/base.pysrc/backend/chat/evals/configs/faithfulness_rag.pysrc/backend/chat/evals/configs/self_documentation.pysrc/backend/chat/evals/dashboard.pysrc/backend/chat/evals/dashboard/dashboard.htmlsrc/backend/chat/evals/production_agent.pysrc/backend/chat/evals/report_builder.pysrc/backend/chat/evals/storage.pysrc/backend/chat/management/commands/compare_evals.pysrc/backend/chat/management/commands/run_evals.pysrc/backend/chat/tests/evals/test_compare.pysrc/backend/chat/tests/evals/test_production_agent.pysrc/backend/chat/tests/evals/test_report_builder.pysrc/backend/chat/tests/evals/test_storage.py
🚧 Files skipped from review as they are similar to previous changes (9)
- src/backend/chat/evals/configs/base.py
- src/backend/chat/tests/evals/test_storage.py
- src/backend/chat/management/commands/compare_evals.py
- src/backend/chat/evals/dashboard.py
- Makefile
- src/backend/chat/evals/configs/self_documentation.py
- src/backend/chat/evals/report_builder.py
- src/backend/chat/evals/production_agent.py
- src/backend/chat/evals/dashboard/dashboard.html
a698088 to
e5baf5b
Compare
bdfe3cb to
ad951fa
Compare
There was a problem hiding this comment.
Actionable comments posted: 4
♻️ Duplicate comments (3)
src/backend/chat/evals/storage.py (3)
41-54: 🩺 Stability & Availability | 🔴 Critical | ⚡ Quick winSame invalid
exceptsyntax repeated in_run_git.Identical issue as
_git_root— apply the same tuple fix here.🐛 Proposed fix
- except subprocess.CalledProcessError, FileNotFoundError: + except (subprocess.CalledProcessError, FileNotFoundError): return None🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/backend/chat/evals/storage.py` around lines 41 - 54, The _run_git helper still uses invalid multi-exception syntax in its except block, matching the same issue as _git_root. Update the exception handling in _run_git to catch both subprocess.CalledProcessError and FileNotFoundError using the correct tuple form, keeping the fallback return None behavior unchanged. Use the _run_git function name to locate the fix.
25-38: 🩺 Stability & Availability | 🔴 Critical | ⚡ Quick win
SyntaxError: invalid Python 2 multi-exception syntax breaks module import.
except subprocess.CalledProcessError, FileNotFoundError:is invalid in Python 3 — must be a tuple. This raisesSyntaxErrorat import time, breaking the entirechat.evals.storagemodule (and by extensionrun_evals,save_run). A prior review flagged this same issue and it was reported as addressed, but the code shown here still has the bug — please re-verify the fix landed on this branch.🐛 Proposed fix
- except subprocess.CalledProcessError, FileNotFoundError: + except (subprocess.CalledProcessError, FileNotFoundError): return None🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/backend/chat/evals/storage.py` around lines 25 - 38, The `_git_root` helper still uses invalid Python 2 exception syntax, which prevents `chat.evals.storage` from importing. Update the `except` clause in `_git_root` to use Python 3 multi-exception syntax with a tuple, keeping the existing fallback behavior that returns `None` on `subprocess.CalledProcessError` or `FileNotFoundError`. Verify the fix in the `storage.py` module so `run_evals` and `save_run` can import successfully.
94-98: 🗄️ Data Integrity & Integration | 🟡 Minor | ⚡ Quick winRun-ID collisions still possible at second precision.
make_run_idstill uses second-precision timestamps; two runs within the same second (same commit or bothnogit) generate identical IDs, sosave_runoverwrites the earlier run file. This was flagged previously and remains unresolved.🛠️ Proposed fix
def make_run_id(git_short: str | None) -> str: """Generate a unique run ID based on timestamp and Git commit short.""" - timestamp = datetime.now(UTC).strftime("%Y-%m-%dT%H-%M-%SZ") + timestamp = datetime.now(UTC).strftime("%Y-%m-%dT%H-%M-%S-%fZ") suffix = git_short or "nogit" return f"{timestamp}_{suffix}"🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/backend/chat/evals/storage.py` around lines 94 - 98, make_run_id still generates run IDs at second precision, so save_run can overwrite a previous run when two executions happen in the same second; update make_run_id to include finer-grained uniqueness (for example, sub-second time and/or a random or monotonic component) while keeping the existing git_short/nogit suffix, and ensure save_run continues to use the returned ID so each run file name stays unique.
🧹 Nitpick comments (4)
src/backend/chat/management/commands/reset_evals.py (1)
5-5: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winConstants imported by value can't be patched via
chat.evals.storage.
BASELINES_DIR,DASHBOARD_DIR, andRUNS_DIRare imported directly (from chat.evals.storage import ...), binding local names in this module. Per pytest's monkeypatch guidance, tests must patch the name where it's used, i.e.chat.management.commands.reset_evals.RUNS_DIR, notchat.evals.storage.RUNS_DIR— patching the latter (as done intest_storage.py) would have no effect here. Referencing the storage module directly (storage.RUNS_DIR) instead of importing the names would avoid this footgun and keep a single source of truth.♻️ Suggested refactor
-from chat.evals.storage import BASELINES_DIR, DASHBOARD_DIR, RUNS_DIR, reset_eval_artifacts +from chat.evals import storage +from chat.evals.storage import reset_eval_artifactsThen reference
storage.RUNS_DIR,storage.BASELINES_DIR,storage.DASHBOARD_DIRthroughout.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/backend/chat/management/commands/reset_evals.py` at line 5, The command module is importing BASELINES_DIR, DASHBOARD_DIR, and RUNS_DIR by value from chat.evals.storage, which prevents tests from monkeypatching the source module and creates a separate binding. Update reset_evals to reference the storage module directly (for example via chat.evals.storage or an alias) and use storage.RUNS_DIR, storage.BASELINES_DIR, and storage.DASHBOARD_DIR throughout, keeping reset_eval_artifacts and the command logic on a single source of truth.src/backend/chat/management/commands/run_evals.py (1)
20-21: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low valueRemove leftover commented-out debug lines.
🧹 Proposed fix
-# settings.LLM_DEFAULT_MODEL_HRID = "etalab-plateform-mistral-medium-2508" -# settings.LANGFUSE_ENABLED = False🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/backend/chat/management/commands/run_evals.py` around lines 20 - 21, Remove the leftover commented-out debug settings in the run_evals command by deleting the two inactive settings lines near the top of run_evals.py; keep the command clean and only leave the active logic in the module.Source: Linters/SAST tools
src/backend/chat/evals/dashboard/template.html (1)
786-789: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low valueRedundant nested ternary always resolves to the same value.
Every branch of
kind === "regression" || kind === "partial_down" ? "row-changed" : kind === "improvement" || kind === "partial_up" ? "row-changed" : "row-changed"returns"row-changed", so this is equivalent to a single fallback value. SonarCloud also flags this (line 788: conditional returns the same value regardless of condition).♻️ Proposed simplification
- const rowClass = kind === "stable" ? (a.passed ? "row-pass" : "row-fail") - : kind === "regression" || kind === "partial_down" ? "row-changed" - : kind === "improvement" || kind === "partial_up" ? "row-changed" : "row-changed"; + const rowClass = kind === "stable" ? (a.passed ? "row-pass" : "row-fail") : "row-changed";🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/backend/chat/evals/dashboard/template.html` around lines 786 - 789, The rowClass ternary in template.html is redundant because every non-stable branch returns the same "row-changed" value. Simplify the conditional around rowClass in the dashboard template so it only checks for "stable" versus the fallback, using the existing rowClass and caseDelta logic as the anchor points.Source: Linters/SAST tools
src/backend/chat/tests/evals/test_production_agent.py (1)
36-66: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winExtract shared fixtures into a conftest.py.
reset_eval_session_cache_fixtureandai_settings_fixtureare duplicated verbatim intest_tool_selection.py. Moving them to aconftest.pyunderchat/tests/evals/would avoid drift as more eval test modules are added.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/backend/chat/tests/evals/test_production_agent.py` around lines 36 - 66, The two autouse fixtures in this test module are duplicated in another eval test file, so they should be centralized. Move reset_eval_session_cache_fixture and ai_settings_fixture into a conftest.py under chat/tests/evals/, keeping their behavior the same and ensuring the test modules still pick them up automatically via pytest fixture discovery.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@src/backend/chat/evals/compare.py`:
- Around line 112-207: SonarCloud is failing because compare_runs and
format_comparison are still too cognitively complex. Refactor compare_runs by
extracting the per-dataset and per-case branching into focused helpers such as
_compare_dataset and _diff_cases, and move any repeated missing-case /
hash-mismatch handling out of the main loop. In format_comparison, finish
extracting the render logic into top-level helpers like render_case_change and
render_dataset_comparison so the main function becomes a simple orchestration
path.
In `@src/backend/chat/evals/dashboard/template.html`:
- Around line 99-100: The partial-change badge styles in template.html use
colors in the .badge-partial-up and .badge-partial-down rules that fail minimum
contrast requirements. Update those badge color pairs to higher-contrast
foreground/background combinations while keeping the same badge classes and
preserving the existing “↑ partiel” and “↓ partiel” styling intent.
- Around line 544-584: `compareRuns` and `renderRunDetail` are too complex for
SonarCloud’s threshold; simplify them by extracting the per-dataset/per-case
processing into focused helpers. In `compareRuns`, move the row-building logic
around the datasets/cases iteration into a helper that returns the row data and
counters, and keep `compareRuns` limited to orchestration using `caseDelta`. In
`renderRunDetail`, extract the logic that renders each case row and builds the
`reasons` output into small helper functions so the main method only coordinates
the table rendering and summary assembly.
In `@src/backend/chat/management/commands/reset_evals.py`:
- Around line 46-55: _update `_print_dry_run` in `reset_evals.py` so
`reset_eval_artifacts` dry-run output matches the real behavior: `index.json`
should not be reported as a deletion. Use the existing
`RUNS_DIR`/`_print_dry_run` logic to list only files that are actually unlinked,
and represent the index reset separately as an in-place update consistent with
`src/backend/chat/evals/storage.py`’s `reset_eval_artifacts` and `_save_index`
behavior._
---
Duplicate comments:
In `@src/backend/chat/evals/storage.py`:
- Around line 41-54: The _run_git helper still uses invalid multi-exception
syntax in its except block, matching the same issue as _git_root. Update the
exception handling in _run_git to catch both subprocess.CalledProcessError and
FileNotFoundError using the correct tuple form, keeping the fallback return None
behavior unchanged. Use the _run_git function name to locate the fix.
- Around line 25-38: The `_git_root` helper still uses invalid Python 2
exception syntax, which prevents `chat.evals.storage` from importing. Update the
`except` clause in `_git_root` to use Python 3 multi-exception syntax with a
tuple, keeping the existing fallback behavior that returns `None` on
`subprocess.CalledProcessError` or `FileNotFoundError`. Verify the fix in the
`storage.py` module so `run_evals` and `save_run` can import successfully.
- Around line 94-98: make_run_id still generates run IDs at second precision, so
save_run can overwrite a previous run when two executions happen in the same
second; update make_run_id to include finer-grained uniqueness (for example,
sub-second time and/or a random or monotonic component) while keeping the
existing git_short/nogit suffix, and ensure save_run continues to use the
returned ID so each run file name stays unique.
---
Nitpick comments:
In `@src/backend/chat/evals/dashboard/template.html`:
- Around line 786-789: The rowClass ternary in template.html is redundant
because every non-stable branch returns the same "row-changed" value. Simplify
the conditional around rowClass in the dashboard template so it only checks for
"stable" versus the fallback, using the existing rowClass and caseDelta logic as
the anchor points.
In `@src/backend/chat/management/commands/reset_evals.py`:
- Line 5: The command module is importing BASELINES_DIR, DASHBOARD_DIR, and
RUNS_DIR by value from chat.evals.storage, which prevents tests from
monkeypatching the source module and creates a separate binding. Update
reset_evals to reference the storage module directly (for example via
chat.evals.storage or an alias) and use storage.RUNS_DIR, storage.BASELINES_DIR,
and storage.DASHBOARD_DIR throughout, keeping reset_eval_artifacts and the
command logic on a single source of truth.
In `@src/backend/chat/management/commands/run_evals.py`:
- Around line 20-21: Remove the leftover commented-out debug settings in the
run_evals command by deleting the two inactive settings lines near the top of
run_evals.py; keep the command clean and only leave the active logic in the
module.
In `@src/backend/chat/tests/evals/test_production_agent.py`:
- Around line 36-66: The two autouse fixtures in this test module are duplicated
in another eval test file, so they should be centralized. Move
reset_eval_session_cache_fixture and ai_settings_fixture into a conftest.py
under chat/tests/evals/, keeping their behavior the same and ensuring the test
modules still pick them up automatically via pytest fixture discovery.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Repository UI
Review profile: CHILL
Plan: Pro
Run ID: 69f63d1a-d105-4f2d-8e80-48224f3cd4a5
📒 Files selected for processing (50)
.gitignoreCHANGELOG.mdMakefilesrc/backend/chat/evals/README.mdsrc/backend/chat/evals/__init__.pysrc/backend/chat/evals/baselines/.gitkeepsrc/backend/chat/evals/baselines/main.jsonsrc/backend/chat/evals/baselines/main_run.jsonsrc/backend/chat/evals/compare.pysrc/backend/chat/evals/configs/__init__.pysrc/backend/chat/evals/configs/base.pysrc/backend/chat/evals/configs/faithfulness_rag.pysrc/backend/chat/evals/configs/incertitude.pysrc/backend/chat/evals/configs/self_documentation.pysrc/backend/chat/evals/configs/tool_selection.pysrc/backend/chat/evals/configs/url_hallucination.pysrc/backend/chat/evals/dashboard.pysrc/backend/chat/evals/dashboard/.gitkeepsrc/backend/chat/evals/dashboard/template.htmlsrc/backend/chat/evals/datasets/faithfulness_rag.yamlsrc/backend/chat/evals/datasets/incertitude.yamlsrc/backend/chat/evals/datasets/self_documentation.yamlsrc/backend/chat/evals/datasets/tool_selection.yamlsrc/backend/chat/evals/datasets/url_hallucination.yamlsrc/backend/chat/evals/evaluators/__init__.pysrc/backend/chat/evals/evaluators/span.pysrc/backend/chat/evals/evaluators/url_regex.pysrc/backend/chat/evals/production_agent.pysrc/backend/chat/evals/report_builder.pysrc/backend/chat/evals/runs/.gitkeepsrc/backend/chat/evals/storage.pysrc/backend/chat/evals/tool_output.pysrc/backend/chat/evals/tool_stub_responses.pysrc/backend/chat/management/commands/compare_evals.pysrc/backend/chat/management/commands/create_eval_baseline.pysrc/backend/chat/management/commands/generate_eval_dashboard.pysrc/backend/chat/management/commands/reset_evals.pysrc/backend/chat/management/commands/run_evals.pysrc/backend/chat/providers/albert_models.pysrc/backend/chat/tests/agents/test_albert_models.pysrc/backend/chat/tests/evals/__init__.pysrc/backend/chat/tests/evals/test_compare.pysrc/backend/chat/tests/evals/test_dashboard.pysrc/backend/chat/tests/evals/test_production_agent.pysrc/backend/chat/tests/evals/test_report_builder.pysrc/backend/chat/tests/evals/test_reset_evals.pysrc/backend/chat/tests/evals/test_storage.pysrc/backend/chat/tests/evals/test_tool_selection.pysrc/backend/chat/tests/evals/test_tool_stub_responses.pysrc/backend/conversations/settings.py
✅ Files skipped from review due to trivial changes (5)
- .gitignore
- src/backend/chat/evals/datasets/faithfulness_rag.yaml
- src/backend/chat/evals/datasets/url_hallucination.yaml
- src/backend/chat/evals/baselines/main_run.json
- src/backend/chat/evals/README.md
🚧 Files skipped from review as they are similar to previous changes (18)
- src/backend/chat/evals/tool_output.py
- src/backend/chat/evals/configs/incertitude.py
- src/backend/chat/evals/configs/init.py
- src/backend/chat/management/commands/generate_eval_dashboard.py
- src/backend/chat/evals/datasets/incertitude.yaml
- src/backend/chat/evals/evaluators/span.py
- src/backend/chat/management/commands/create_eval_baseline.py
- src/backend/chat/evals/configs/base.py
- src/backend/chat/tests/evals/test_compare.py
- src/backend/chat/evals/configs/self_documentation.py
- src/backend/chat/tests/evals/test_report_builder.py
- src/backend/chat/evals/evaluators/init.py
- src/backend/chat/tests/agents/test_albert_models.py
- src/backend/chat/evals/datasets/self_documentation.yaml
- src/backend/chat/providers/albert_models.py
- src/backend/chat/evals/configs/url_hallucination.py
- Makefile
- src/backend/chat/management/commands/compare_evals.py
ad951fa to
4307704
Compare
9514026 to
1eea0cb
Compare
1eea0cb to
8bed35e
Compare
There was a problem hiding this comment.
Actionable comments posted: 2
🧹 Nitpick comments (3)
src/backend/chat/evals/datasets/tool_selection.yaml (1)
8-631: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low valueConsider YAML anchors to reduce evaluator boilerplate.
Nearly every case repeats the identical
HasMatchingSpan/HasNoMatchingSpanblock shape differing only ingen_ai.tool.nameandevaluation_name. YAML anchors/aliases (or merge keys) could cut this duplication significantly and make future edits (e.g., adding a new tool name) less error-prone.♻️ Example using anchors
.web_search_span: &web_search_span query: has_attributes: gen_ai.tool.name: "web_search" cases: - name: news_recent_ai ... evaluators: - HasMatchingSpan: <<: *web_search_span evaluation_name: called_web_searchThis is a nice-to-have; not blocking given the file is data-only and each case is still easy to read individually.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/backend/chat/evals/datasets/tool_selection.yaml` around lines 8 - 631, The evaluator blocks in this YAML are highly repetitive, so refactor the repeated HasMatchingSpan and HasNoMatchingSpan query shapes using YAML anchors/aliases or merge keys. Define reusable anchored query templates for each tool name in the dataset, then reference them across the cases while keeping each case’s evaluation_name explicit; this will reduce boilerplate and make updates in tool_selection.yaml easier to maintain.src/backend/chat/evals/dashboard.py (2)
70-78: 🩺 Stability & Availability | 🔵 Trivial | 💤 Low valueMinor: direct dict indexing for
run_idcould raiseKeyError.
entry["run_id"]is used via direct indexing in the filter (line 71) and loop (line 78) rather than.get("run_id"). If a legacy/malformed index entry lacks this key, dashboard generation will crash instead of degrading gracefully.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/backend/chat/evals/dashboard.py` around lines 70 - 78, The dashboard entry handling in `dashboard.py` uses direct `entry["run_id"]` access in both the `kept_entries` filter and the `for entry in kept_entries` loop, which can raise `KeyError` for malformed records. Update the logic around `kept_entries` and `resolve_run` to read the run id safely with `.get("run_id")`, and skip or ignore entries that do not have a valid run id so dashboard generation continues gracefully.
36-53: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick winConsider isolating per-file YAML parse failures.
yaml.safe_load(raw)has no error handling; a single malformed or hand-edited dataset YAML file will raise and abortload_dataset_catalogentirely, breaking dashboard generation for all datasets rather than just the offending one.♻️ Proposed fix
for yaml_path in sorted(root.glob("*.yaml")): raw = yaml_path.read_text(encoding="utf-8") - data = yaml.safe_load(raw) or {} + try: + data = yaml.safe_load(raw) or {} + except yaml.YAMLError: + continue🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/backend/chat/evals/dashboard.py` around lines 36 - 53, `load_dataset_catalog` currently lets a malformed YAML file from `yaml.safe_load` abort the whole catalog load. Wrap the per-file parse in error handling inside `load_dataset_catalog`, and if parsing fails, skip that yaml_path while continuing to process the remaining files. Keep the existing behavior for valid files, and use the existing symbols `load_dataset_catalog`, `yaml.safe_load`, and `yaml_path` to localize the fix.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@src/backend/chat/evals/compare.py`:
- Around line 282-363: The cognitive complexity of format_comparison is still
too high because the nested helper logic remains inside the function. Extract
_note, render_case_change, and render_dataset_comparison into top-level helpers
that return lists of rendered lines, and update format_comparison to only
orchestrate header, warnings, dataset rendering, and summary assembly using
those helpers. Use the existing symbols RunComparison, change.case_name, and
dataset_comparison to keep the extracted logic easy to locate and preserve the
current output formatting.
In `@src/backend/chat/evals/dashboard/template.html`:
- Around line 550-552: Update caseDelta in template.html so avg_scores
comparisons are order-insensitive instead of relying on JSON.stringify key
order; normalize both b.avg_scores and a.avg_scores before comparing, or compare
their entries by key/value like Python dict equality, while keeping the existing
passed and pass_rate checks unchanged.
---
Nitpick comments:
In `@src/backend/chat/evals/dashboard.py`:
- Around line 70-78: The dashboard entry handling in `dashboard.py` uses direct
`entry["run_id"]` access in both the `kept_entries` filter and the `for entry in
kept_entries` loop, which can raise `KeyError` for malformed records. Update the
logic around `kept_entries` and `resolve_run` to read the run id safely with
`.get("run_id")`, and skip or ignore entries that do not have a valid run id so
dashboard generation continues gracefully.
- Around line 36-53: `load_dataset_catalog` currently lets a malformed YAML file
from `yaml.safe_load` abort the whole catalog load. Wrap the per-file parse in
error handling inside `load_dataset_catalog`, and if parsing fails, skip that
yaml_path while continuing to process the remaining files. Keep the existing
behavior for valid files, and use the existing symbols `load_dataset_catalog`,
`yaml.safe_load`, and `yaml_path` to localize the fix.
In `@src/backend/chat/evals/datasets/tool_selection.yaml`:
- Around line 8-631: The evaluator blocks in this YAML are highly repetitive, so
refactor the repeated HasMatchingSpan and HasNoMatchingSpan query shapes using
YAML anchors/aliases or merge keys. Define reusable anchored query templates for
each tool name in the dataset, then reference them across the cases while
keeping each case’s evaluation_name explicit; this will reduce boilerplate and
make updates in tool_selection.yaml easier to maintain.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Repository UI
Review profile: CHILL
Plan: Pro
Run ID: d59b1cac-3ecf-4742-bcbd-ea6831dc1922
📒 Files selected for processing (50)
.codespellrc.gitignoreCHANGELOG.mdMakefilesrc/backend/chat/evals/README.mdsrc/backend/chat/evals/__init__.pysrc/backend/chat/evals/baselines/.gitkeepsrc/backend/chat/evals/baselines/main.jsonsrc/backend/chat/evals/baselines/main_run.jsonsrc/backend/chat/evals/compare.pysrc/backend/chat/evals/configs/__init__.pysrc/backend/chat/evals/configs/base.pysrc/backend/chat/evals/configs/faithfulness_rag.pysrc/backend/chat/evals/configs/incertitude.pysrc/backend/chat/evals/configs/tool_selection.pysrc/backend/chat/evals/configs/url_hallucination.pysrc/backend/chat/evals/dashboard.pysrc/backend/chat/evals/dashboard/.gitkeepsrc/backend/chat/evals/dashboard/template.htmlsrc/backend/chat/evals/datasets/faithfulness_rag.yamlsrc/backend/chat/evals/datasets/incertitude.yamlsrc/backend/chat/evals/datasets/tool_selection.yamlsrc/backend/chat/evals/datasets/url_hallucination.yamlsrc/backend/chat/evals/evaluators/__init__.pysrc/backend/chat/evals/evaluators/span.pysrc/backend/chat/evals/evaluators/url_regex.pysrc/backend/chat/evals/production_agent.pysrc/backend/chat/evals/report_builder.pysrc/backend/chat/evals/runs/.gitkeepsrc/backend/chat/evals/storage.pysrc/backend/chat/evals/tool_output.pysrc/backend/chat/evals/tool_stub_responses.pysrc/backend/chat/management/commands/compare_evals.pysrc/backend/chat/management/commands/create_eval_baseline.pysrc/backend/chat/management/commands/generate_eval_dashboard.pysrc/backend/chat/management/commands/reset_evals.pysrc/backend/chat/management/commands/run_evals.pysrc/backend/chat/providers/albert_models.pysrc/backend/chat/tests/agents/test_albert_models.pysrc/backend/chat/tests/evals/__init__.pysrc/backend/chat/tests/evals/test_compare.pysrc/backend/chat/tests/evals/test_dashboard.pysrc/backend/chat/tests/evals/test_production_agent.pysrc/backend/chat/tests/evals/test_report_builder.pysrc/backend/chat/tests/evals/test_reset_evals.pysrc/backend/chat/tests/evals/test_storage.pysrc/backend/chat/tests/evals/test_tool_selection.pysrc/backend/chat/tests/evals/test_tool_stub_responses.pysrc/backend/chat/tests/test_assistant_health.pysrc/backend/conversations/settings.py
✅ Files skipped from review due to trivial changes (7)
- .codespellrc
- src/backend/chat/evals/baselines/main.json
- .gitignore
- CHANGELOG.md
- src/backend/chat/tests/test_assistant_health.py
- src/backend/chat/evals/baselines/main_run.json
- src/backend/chat/evals/README.md
🚧 Files skipped from review as they are similar to previous changes (29)
- src/backend/chat/evals/configs/incertitude.py
- src/backend/chat/evals/tool_output.py
- src/backend/chat/evals/datasets/incertitude.yaml
- src/backend/chat/management/commands/create_eval_baseline.py
- src/backend/chat/evals/evaluators/init.py
- src/backend/chat/management/commands/generate_eval_dashboard.py
- src/backend/chat/evals/configs/base.py
- src/backend/chat/evals/configs/url_hallucination.py
- src/backend/chat/evals/configs/tool_selection.py
- src/backend/conversations/settings.py
- src/backend/chat/tests/evals/test_compare.py
- src/backend/chat/tests/evals/test_reset_evals.py
- src/backend/chat/tests/agents/test_albert_models.py
- Makefile
- src/backend/chat/management/commands/reset_evals.py
- src/backend/chat/evals/tool_stub_responses.py
- src/backend/chat/tests/evals/test_storage.py
- src/backend/chat/evals/evaluators/span.py
- src/backend/chat/tests/evals/test_tool_selection.py
- src/backend/chat/tests/evals/test_tool_stub_responses.py
- src/backend/chat/management/commands/compare_evals.py
- src/backend/chat/evals/report_builder.py
- src/backend/chat/providers/albert_models.py
- src/backend/chat/evals/init.py
- src/backend/chat/tests/evals/test_production_agent.py
- src/backend/chat/management/commands/run_evals.py
- src/backend/chat/evals/datasets/faithfulness_rag.yaml
- src/backend/chat/evals/storage.py
- src/backend/chat/evals/production_agent.py
8bed35e to
4066800
Compare
There was a problem hiding this comment.
Actionable comments posted: 1
♻️ Duplicate comments (1)
src/backend/chat/evals/storage.py (1)
27-37: 🩺 Stability & Availability | 🔴 Critical | ⚡ Quick winFix the exception syntax in
src/backend/chat/evals/storage.pyPython 3 rejects
except subprocess.CalledProcessError, FileNotFoundError:as a syntax error, so the module can’t import. Update both occurrences to tuple syntax:Proposed fix
- except subprocess.CalledProcessError, FileNotFoundError: + except (subprocess.CalledProcessError, FileNotFoundError): return None🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/backend/chat/evals/storage.py` around lines 27 - 37, The exception handling in storage.py uses Python 2 comma syntax, which breaks import in Python 3. Update both try/except blocks around the git root lookup and any similar handling in the same module to use tuple-based except syntax with subprocess.CalledProcessError and FileNotFoundError, so the storage helpers can import and run correctly.
🧹 Nitpick comments (2)
src/backend/chat/evals/dashboard/template.html (1)
730-737: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low valueNested ternary flagged by SonarCloud.
deltaHtml/fillBuse nested ternaries; not CI-blocking (warning, not failure) but worth flattening for readability, consistent with the earlier cognitive-complexity cleanup in this file.♻️ Suggested refactor
- const deltaHtml = pp == null - ? '<span class="muted">—</span>' - : pp === 0 - ? '<span class="muted">=</span>' - : `<span class="${pp > 0 ? "delta-pos" : "delta-neg"}">${pp > 0 ? "+" : ""}${pp}pp</span>`; + let deltaHtml = '<span class="muted">—</span>'; + if (pp !== null) { + deltaHtml = pp === 0 + ? '<span class="muted">=</span>' + : `<span class="${pp > 0 ? "delta-pos" : "delta-neg"}">${pp > 0 ? "+" : ""}${pp}pp</span>`; + }🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/backend/chat/evals/dashboard/template.html` around lines 730 - 737, The summaryRow function contains nested ternaries in deltaHtml and fillB that should be flattened for readability. Refactor the logic in summaryRow to use simple if/else branches or small helper variables instead of chained ternary expressions, keeping the same behavior for null, zero, positive, and negative values.Source: Linters/SAST tools
src/backend/chat/tests/evals/test_storage.py (1)
37-86: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winConsider adding coverage for multiple named baselines.
These tests only exercise a single baseline name. Once the
is_baselineflag bug instorage.py'sset_baseline(clobbering other named baselines' flags) is fixed, a regression test assertingis_baselinestaysTruefor a previously-set baseline after promoting a different one would guard against reintroduction.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/backend/chat/tests/evals/test_storage.py` around lines 37 - 86, Add a regression test around set_baseline in chat.evals.storage that covers promoting multiple named baselines. Reuse the existing test_set_baseline_copies_run_snapshot setup, then set one baseline, promote a different baseline, and assert the first baseline’s is_baseline flag remains True while the new one is also marked correctly. Locate the behavior in set_baseline and verify the index.json state after each promotion to catch the clobbering bug.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@src/backend/chat/evals/storage.py`:
- Around line 246-251: The baseline update logic in set_baseline is overwriting
entry["is_baseline"] for every run based only on the newly promoted record,
which breaks previously named baselines. Update the storage/index refresh in
storage.py so each run’s is_baseline is derived from whether its run_id appears
in index["baselines"] values (or equivalent registry check), not just
record["run_id"]. Keep the existing index["baselines"][baseline_name]
assignment, but make the loop in set_baseline preserve any other runs already
registered as baselines.
---
Duplicate comments:
In `@src/backend/chat/evals/storage.py`:
- Around line 27-37: The exception handling in storage.py uses Python 2 comma
syntax, which breaks import in Python 3. Update both try/except blocks around
the git root lookup and any similar handling in the same module to use
tuple-based except syntax with subprocess.CalledProcessError and
FileNotFoundError, so the storage helpers can import and run correctly.
---
Nitpick comments:
In `@src/backend/chat/evals/dashboard/template.html`:
- Around line 730-737: The summaryRow function contains nested ternaries in
deltaHtml and fillB that should be flattened for readability. Refactor the logic
in summaryRow to use simple if/else branches or small helper variables instead
of chained ternary expressions, keeping the same behavior for null, zero,
positive, and negative values.
In `@src/backend/chat/tests/evals/test_storage.py`:
- Around line 37-86: Add a regression test around set_baseline in
chat.evals.storage that covers promoting multiple named baselines. Reuse the
existing test_set_baseline_copies_run_snapshot setup, then set one baseline,
promote a different baseline, and assert the first baseline’s is_baseline flag
remains True while the new one is also marked correctly. Locate the behavior in
set_baseline and verify the index.json state after each promotion to catch the
clobbering bug.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Repository UI
Review profile: CHILL
Plan: Pro
Run ID: 6b83a32b-6200-41d8-a984-e0a5fc99fee5
📒 Files selected for processing (50)
.codespellrc.gitignoreCHANGELOG.mdMakefilesrc/backend/chat/evals/README.mdsrc/backend/chat/evals/__init__.pysrc/backend/chat/evals/baselines/.gitkeepsrc/backend/chat/evals/baselines/main.jsonsrc/backend/chat/evals/baselines/main_run.jsonsrc/backend/chat/evals/compare.pysrc/backend/chat/evals/configs/__init__.pysrc/backend/chat/evals/configs/base.pysrc/backend/chat/evals/configs/faithfulness_rag.pysrc/backend/chat/evals/configs/incertitude.pysrc/backend/chat/evals/configs/tool_selection.pysrc/backend/chat/evals/configs/url_hallucination.pysrc/backend/chat/evals/dashboard.pysrc/backend/chat/evals/dashboard/.gitkeepsrc/backend/chat/evals/dashboard/template.htmlsrc/backend/chat/evals/datasets/faithfulness_rag.yamlsrc/backend/chat/evals/datasets/incertitude.yamlsrc/backend/chat/evals/datasets/tool_selection.yamlsrc/backend/chat/evals/datasets/url_hallucination.yamlsrc/backend/chat/evals/evaluators/__init__.pysrc/backend/chat/evals/evaluators/span.pysrc/backend/chat/evals/evaluators/url_regex.pysrc/backend/chat/evals/production_agent.pysrc/backend/chat/evals/report_builder.pysrc/backend/chat/evals/runs/.gitkeepsrc/backend/chat/evals/storage.pysrc/backend/chat/evals/tool_output.pysrc/backend/chat/evals/tool_stub_responses.pysrc/backend/chat/management/commands/compare_evals.pysrc/backend/chat/management/commands/create_eval_baseline.pysrc/backend/chat/management/commands/generate_eval_dashboard.pysrc/backend/chat/management/commands/reset_evals.pysrc/backend/chat/management/commands/run_evals.pysrc/backend/chat/providers/albert_models.pysrc/backend/chat/tests/agents/test_albert_models.pysrc/backend/chat/tests/evals/__init__.pysrc/backend/chat/tests/evals/test_compare.pysrc/backend/chat/tests/evals/test_dashboard.pysrc/backend/chat/tests/evals/test_production_agent.pysrc/backend/chat/tests/evals/test_report_builder.pysrc/backend/chat/tests/evals/test_reset_evals.pysrc/backend/chat/tests/evals/test_storage.pysrc/backend/chat/tests/evals/test_tool_selection.pysrc/backend/chat/tests/evals/test_tool_stub_responses.pysrc/backend/chat/tests/test_assistant_health.pysrc/backend/conversations/settings.py
✅ Files skipped from review due to trivial changes (8)
- .codespellrc
- src/backend/chat/evals/evaluators/init.py
- CHANGELOG.md
- .gitignore
- src/backend/chat/evals/baselines/main_run.json
- src/backend/chat/tests/test_assistant_health.py
- src/backend/chat/evals/datasets/faithfulness_rag.yaml
- src/backend/chat/evals/README.md
🚧 Files skipped from review as they are similar to previous changes (30)
- src/backend/chat/evals/tool_output.py
- src/backend/chat/evals/init.py
- src/backend/chat/evals/configs/incertitude.py
- src/backend/chat/evals/datasets/incertitude.yaml
- src/backend/chat/management/commands/generate_eval_dashboard.py
- src/backend/chat/evals/configs/init.py
- src/backend/chat/providers/albert_models.py
- src/backend/chat/evals/configs/url_hallucination.py
- src/backend/chat/tests/evals/test_report_builder.py
- src/backend/chat/evals/baselines/main.json
- src/backend/chat/evals/configs/base.py
- src/backend/chat/evals/datasets/url_hallucination.yaml
- src/backend/chat/evals/configs/tool_selection.py
- src/backend/chat/tests/agents/test_albert_models.py
- src/backend/chat/tests/evals/test_production_agent.py
- src/backend/chat/evals/evaluators/span.py
- src/backend/chat/evals/configs/faithfulness_rag.py
- src/backend/chat/tests/evals/test_tool_selection.py
- src/backend/conversations/settings.py
- src/backend/chat/management/commands/create_eval_baseline.py
- Makefile
- src/backend/chat/management/commands/compare_evals.py
- src/backend/chat/tests/evals/test_compare.py
- src/backend/chat/tests/evals/test_dashboard.py
- src/backend/chat/evals/report_builder.py
- src/backend/chat/evals/datasets/tool_selection.yaml
- src/backend/chat/management/commands/reset_evals.py
- src/backend/chat/evals/production_agent.py
- src/backend/chat/evals/compare.py
- src/backend/chat/management/commands/run_evals.py
a4138b2 to
4d1dbcc
Compare
4d1dbcc to
46c9c0f
Compare
There was a problem hiding this comment.
Actionable comments posted: 1
🧹 Nitpick comments (2)
src/backend/chat/tests/evals/test_compare.py (1)
115-131: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winConsider covering the
partial_downbranch too.
partial_upandpartial_downare the two halves of the same conditional incompare.py, andpartial_downis the one that signals degradation. A mirrored case is nearly free here.♻️ Suggested companion test
+def test_kind_partial_down_when_both_fail_and_rate_drops(): + """Both runs fail but the repeat pass rate drops: kind is partial_down.""" + partial_before = { + "name": "p", + "passed": False, + "pass_rate": 0.8, + "avg_scores": {}, + "reasons": {}, + } + partial_after = {**partial_before, "pass_rate": 0.2} + before = _run(run_id="before", datasets={"ds": _dataset(pass_rate=0.0, cases=[partial_before])}) + after = _run(run_id="after", datasets={"ds": _dataset(pass_rate=0.0, cases=[partial_after])}) + + payload = compare_runs(before, after).to_payload() + + kinds = {c["name"]: c["kind"] for c in payload["datasets"]["ds"]["case_changes"]} + assert kinds == {"p": "partial_down"}🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/backend/chat/tests/evals/test_compare.py` around lines 115 - 131, Add a mirrored regression test alongside test_kind_partial_up_when_both_fail_but_rate_improves for the partial_down branch in compare_runs: keep both runs failing, lower the case pass_rate in the after result, and assert the resulting case_changes entry for "p" has kind "partial_down".src/backend/chat/evals/dashboard/template.html (1)
716-723: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winExtract nested ternary in
summaryRow(SonarCloud flag).
deltaHtml's nested ternary is still flagged by SonarCloud. Extracting to an if/else or a small helper improves readability and clears the CI warning.♻️ Proposed refactor
function summaryRow(label, rateA, rateB, { isTotal = false } = {}) { const pp = rateA != null && rateB != null ? Math.round((rateB - rateA) * 100) : null; - const deltaHtml = pp == null - ? '<span class="muted">—</span>' - : pp === 0 - ? '<span class="muted">=</span>' - : `<span class="${pp > 0 ? "delta-pos" : "delta-neg"}">${pp > 0 ? "+" : ""}${pp}pp</span>`; + let deltaHtml; + if (pp == null) { + deltaHtml = '<span class="muted">—</span>'; + } else if (pp === 0) { + deltaHtml = '<span class="muted">=</span>'; + } else { + deltaHtml = `<span class="${pp > 0 ? "delta-pos" : "delta-neg"}">${pp > 0 ? "+" : ""}${pp}pp</span>`; + }🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/backend/chat/evals/dashboard/template.html` around lines 716 - 723, Refactor the nested ternary used to build deltaHtml inside summaryRow into explicit if/else logic or a small helper, preserving the existing null, zero, positive, and negative output markup and classes while eliminating SonarCloud’s nested-ternary warning.Source: Linters/SAST tools
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@src/backend/chat/evals/datasets/tool_selection.yaml`:
- Around line 908-921: Update the tool_output fixture for rapport-eval.pdf so
its summary and web_search snippets use the shared timeline: production in Q4
2025, with the March 2025 internal revision context, and remove the inconsistent
Q4 2027/T1 2028 dates while preserving the existing JSON structure.
---
Nitpick comments:
In `@src/backend/chat/evals/dashboard/template.html`:
- Around line 716-723: Refactor the nested ternary used to build deltaHtml
inside summaryRow into explicit if/else logic or a small helper, preserving the
existing null, zero, positive, and negative output markup and classes while
eliminating SonarCloud’s nested-ternary warning.
In `@src/backend/chat/tests/evals/test_compare.py`:
- Around line 115-131: Add a mirrored regression test alongside
test_kind_partial_up_when_both_fail_but_rate_improves for the partial_down
branch in compare_runs: keep both runs failing, lower the case pass_rate in the
after result, and assert the resulting case_changes entry for "p" has kind
"partial_down".
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Repository UI
Review profile: CHILL
Plan: Pro Plus
Run ID: dd88ae99-790e-474f-8d13-628b9b33a0ed
📒 Files selected for processing (52)
.codespellrc.gitignoreCHANGELOG.mdMakefilesrc/backend/.pylintrcsrc/backend/chat/evals/README.mdsrc/backend/chat/evals/__init__.pysrc/backend/chat/evals/baselines/.gitkeepsrc/backend/chat/evals/baselines/main.jsonsrc/backend/chat/evals/baselines/main_run.jsonsrc/backend/chat/evals/compare.pysrc/backend/chat/evals/configs/__init__.pysrc/backend/chat/evals/configs/base.pysrc/backend/chat/evals/configs/faithfulness_rag.pysrc/backend/chat/evals/configs/incertitude.pysrc/backend/chat/evals/configs/tool_selection.pysrc/backend/chat/evals/configs/url_hallucination.pysrc/backend/chat/evals/dashboard.pysrc/backend/chat/evals/dashboard/.gitkeepsrc/backend/chat/evals/dashboard/template.htmlsrc/backend/chat/evals/datasets/faithfulness_rag.yamlsrc/backend/chat/evals/datasets/incertitude.yamlsrc/backend/chat/evals/datasets/tool_selection.yamlsrc/backend/chat/evals/datasets/url_hallucination.yamlsrc/backend/chat/evals/evaluators/__init__.pysrc/backend/chat/evals/evaluators/span.pysrc/backend/chat/evals/evaluators/url_regex.pysrc/backend/chat/evals/production_agent.pysrc/backend/chat/evals/report_builder.pysrc/backend/chat/evals/runs/.gitkeepsrc/backend/chat/evals/storage.pysrc/backend/chat/evals/tool_output.pysrc/backend/chat/evals/tool_stub_responses.pysrc/backend/chat/management/commands/compare_evals.pysrc/backend/chat/management/commands/create_eval_baseline.pysrc/backend/chat/management/commands/generate_eval_dashboard.pysrc/backend/chat/management/commands/reset_evals.pysrc/backend/chat/management/commands/run_evals.pysrc/backend/chat/providers/albert_models.pysrc/backend/chat/tests/agents/test_albert_models.pysrc/backend/chat/tests/evals/__init__.pysrc/backend/chat/tests/evals/test_compare.pysrc/backend/chat/tests/evals/test_configs.pysrc/backend/chat/tests/evals/test_dashboard.pysrc/backend/chat/tests/evals/test_production_agent.pysrc/backend/chat/tests/evals/test_report_builder.pysrc/backend/chat/tests/evals/test_reset_evals.pysrc/backend/chat/tests/evals/test_storage.pysrc/backend/chat/tests/evals/test_tool_selection.pysrc/backend/chat/tests/evals/test_tool_stub_responses.pysrc/backend/chat/tests/test_assistant_health.pysrc/backend/conversations/settings.py
🚧 Files skipped from review as they are similar to previous changes (29)
- src/backend/chat/evals/evaluators/init.py
- .codespellrc
- src/backend/chat/management/commands/generate_eval_dashboard.py
- src/backend/chat/evals/evaluators/span.py
- src/backend/chat/evals/init.py
- src/backend/chat/tests/evals/test_report_builder.py
- .gitignore
- src/backend/chat/evals/tool_output.py
- src/backend/chat/evals/configs/init.py
- src/backend/chat/evals/datasets/url_hallucination.yaml
- src/backend/chat/management/commands/create_eval_baseline.py
- CHANGELOG.md
- src/backend/conversations/settings.py
- src/backend/chat/management/commands/reset_evals.py
- src/backend/chat/management/commands/compare_evals.py
- src/backend/chat/evals/datasets/incertitude.yaml
- src/backend/chat/tests/test_assistant_health.py
- src/backend/chat/providers/albert_models.py
- src/backend/chat/tests/agents/test_albert_models.py
- src/backend/chat/evals/README.md
- src/backend/chat/evals/datasets/faithfulness_rag.yaml
- Makefile
- src/backend/chat/tests/evals/test_production_agent.py
- src/backend/chat/evals/report_builder.py
- src/backend/chat/evals/compare.py
- src/backend/chat/tests/evals/test_tool_selection.py
- src/backend/chat/evals/configs/tool_selection.py
- src/backend/chat/evals/production_agent.py
- src/backend/chat/management/commands/run_evals.py
07c200b to
205e2bd
Compare
There was a problem hiding this comment.
Actionable comments posted: 3
♻️ Duplicate comments (1)
src/backend/chat/evals/storage.py (1)
28-37: 🩺 Stability & Availability | 🔴 Critical | ⚡ Quick winUse Python 3 exception tuple syntax.
storage.pyis still parsed as Python 3, soexcept subprocess.CalledProcessError, FileNotFoundError:is invalid syntax at both lines 36 and 52 and prevents the module from importing.🐛 Proposed fix (apply to both lines 36 and 52)
- except subprocess.CalledProcessError, FileNotFoundError: + except (subprocess.CalledProcessError, FileNotFoundError): return None🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/backend/chat/evals/storage.py` around lines 28 - 37, Update both exception handlers in the storage path-resolution logic to use Python 3 tuple syntax, replacing the comma-separated exception form in the relevant try/except blocks while preserving the existing return None behavior.
🧹 Nitpick comments (3)
src/backend/chat/tests/agents/test_albert_models.py (1)
283-310: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winConsider covering the non-dict
tool_calldefensive branch.
_validate_completionguards againsttool_callentries that aren't dicts (if not isinstance(tool_call, dict): continueinalbert_models.py), but no test here exercises that path. Consider a case wherechoices[0].message.tool_callscontains a non-dict entry to lock in that defensive behavior.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/backend/chat/tests/agents/test_albert_models.py` around lines 283 - 310, Add a focused test alongside the existing _validate_completion tests that builds a completion whose choices[0].message.tool_calls contains a non-dict entry, then calls albert_model._validate_completion and verifies validation completes without error while preserving or skipping that entry according to the existing defensive behavior. Reuse _make_malformed_chat_completion and the established completion/message fixtures or symbols rather than adding unrelated coverage.src/backend/chat/evals/dashboard/template.html (1)
76-76: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winLow-contrast header subtitle text.
.site-header-subtitleuses#94a3b8on the dark.site-headergradient background, flagged by SonarCloud as failing minimum contrast.💡 Proposed fix
.site-header-subtitle { margin: .45rem 0 0; max-width: 36rem; font-size: .92rem; line-height: 1.45; - color: `#94a3b8`; + color: `#cbd5e1`; }🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/backend/chat/evals/dashboard/template.html` at line 76, Update the .site-header-subtitle color from `#94a3b8` to a lighter color that meets SonarCloud’s minimum contrast requirement against the dark .site-header gradient, while preserving the existing header styling.Source: Linters/SAST tools
src/backend/chat/providers/albert_models.py (1)
78-108: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winExtract the tool-call normalization loop to satisfy the SonarCloud complexity gate.
Cognitive complexity is 16 vs the 15 allowed (CI gate failure). Pulling the inner
for tool_call in ...loop into a small helper keeps_validate_completionas orchestration only.♻️ Proposed refactor
+ `@staticmethod` + def _normalize_tool_call_types(choices: list[dict]) -> None: + for choice in choices: + for tool_call in (choice.get("message") or {}).get("tool_calls") or []: + if not isinstance(tool_call, dict): + continue + is_custom = tool_call.get("type") == "custom" and "custom" in tool_call + if not is_custom and tool_call.get("type") != "function": + tool_call["type"] = "function" + def _validate_completion(self, response: chat.ChatCompletion) -> _ChatCompletion: ... if not isinstance(data.get("choices"), list): data["choices"] = [] - - for choice in data.get("choices") or []: - for tool_call in (choice.get("message") or {}).get("tool_calls") or []: - if not isinstance(tool_call, dict): - continue - is_custom = tool_call.get("type") == "custom" and "custom" in tool_call - if not is_custom and tool_call.get("type") != "function": - tool_call["type"] = "function" - + self._normalize_tool_call_types(data["choices"]) return _ChatCompletion.model_validate(data)🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/backend/chat/providers/albert_models.py` around lines 78 - 108, Extract the inner tool-call normalization loop from _validate_completion into a small dedicated helper that accepts the relevant message or tool-call collection and preserves the existing custom/function type rules. Have _validate_completion delegate to this helper while retaining only response normalization and orchestration, reducing its cognitive complexity without changing behavior.Source: Linters/SAST tools
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@CHANGELOG.md`:
- Line 397: Move the “add run_evals management command” changelog entry from the
version 0.0.1 section into the existing “## [unreleased]” section, preserving
its text and ensuring the existing Line 399 reference definition remains used.
In `@src/backend/chat/management/commands/run_evals.py`:
- Around line 86-91: Extend the validation around the save option in the
run-evals command so --save cannot be combined with either --case or --dataset.
Preserve the existing CommandError behavior and message context, updating it to
identify both filters as unsupported for saved partial runs.
- Line 106: Update the argument validation in the eval command before the
REGISTRY selection so providing --case without --dataset raises a clear
CommandError. Keep the existing configs selection unchanged when --dataset is
supplied or when running without a case filter.
---
Duplicate comments:
In `@src/backend/chat/evals/storage.py`:
- Around line 28-37: Update both exception handlers in the storage
path-resolution logic to use Python 3 tuple syntax, replacing the
comma-separated exception form in the relevant try/except blocks while
preserving the existing return None behavior.
---
Nitpick comments:
In `@src/backend/chat/evals/dashboard/template.html`:
- Line 76: Update the .site-header-subtitle color from `#94a3b8` to a lighter
color that meets SonarCloud’s minimum contrast requirement against the dark
.site-header gradient, while preserving the existing header styling.
In `@src/backend/chat/providers/albert_models.py`:
- Around line 78-108: Extract the inner tool-call normalization loop from
_validate_completion into a small dedicated helper that accepts the relevant
message or tool-call collection and preserves the existing custom/function type
rules. Have _validate_completion delegate to this helper while retaining only
response normalization and orchestration, reducing its cognitive complexity
without changing behavior.
In `@src/backend/chat/tests/agents/test_albert_models.py`:
- Around line 283-310: Add a focused test alongside the existing
_validate_completion tests that builds a completion whose
choices[0].message.tool_calls contains a non-dict entry, then calls
albert_model._validate_completion and verifies validation completes without
error while preserving or skipping that entry according to the existing
defensive behavior. Reuse _make_malformed_chat_completion and the established
completion/message fixtures or symbols rather than adding unrelated coverage.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Repository UI
Review profile: CHILL
Plan: Pro Plus
Run ID: 4bb2209f-7a0a-4cc5-b30b-4d0be810bcd8
⛔ Files ignored due to path filters (1)
src/backend/uv.lockis excluded by!**/*.lock
📒 Files selected for processing (52)
.codespellrc.gitignoreCHANGELOG.mdMakefilesrc/backend/.pylintrcsrc/backend/chat/evals/README.mdsrc/backend/chat/evals/__init__.pysrc/backend/chat/evals/baselines/.gitkeepsrc/backend/chat/evals/baselines/main.jsonsrc/backend/chat/evals/baselines/main_run.jsonsrc/backend/chat/evals/compare.pysrc/backend/chat/evals/configs/__init__.pysrc/backend/chat/evals/configs/base.pysrc/backend/chat/evals/configs/faithfulness_rag.pysrc/backend/chat/evals/configs/incertitude.pysrc/backend/chat/evals/configs/tool_selection.pysrc/backend/chat/evals/configs/url_hallucination.pysrc/backend/chat/evals/dashboard.pysrc/backend/chat/evals/dashboard/.gitkeepsrc/backend/chat/evals/dashboard/template.htmlsrc/backend/chat/evals/datasets/faithfulness_rag.yamlsrc/backend/chat/evals/datasets/incertitude.yamlsrc/backend/chat/evals/datasets/tool_selection.yamlsrc/backend/chat/evals/datasets/url_hallucination.yamlsrc/backend/chat/evals/evaluators/__init__.pysrc/backend/chat/evals/evaluators/span.pysrc/backend/chat/evals/evaluators/url_regex.pysrc/backend/chat/evals/production_agent.pysrc/backend/chat/evals/report_builder.pysrc/backend/chat/evals/runs/.gitkeepsrc/backend/chat/evals/storage.pysrc/backend/chat/evals/tool_output.pysrc/backend/chat/evals/tool_stub_responses.pysrc/backend/chat/management/commands/compare_evals.pysrc/backend/chat/management/commands/create_eval_baseline.pysrc/backend/chat/management/commands/generate_eval_dashboard.pysrc/backend/chat/management/commands/reset_evals.pysrc/backend/chat/management/commands/run_evals.pysrc/backend/chat/providers/albert_models.pysrc/backend/chat/tests/agents/test_albert_models.pysrc/backend/chat/tests/evals/__init__.pysrc/backend/chat/tests/evals/test_compare.pysrc/backend/chat/tests/evals/test_configs.pysrc/backend/chat/tests/evals/test_dashboard.pysrc/backend/chat/tests/evals/test_production_agent.pysrc/backend/chat/tests/evals/test_report_builder.pysrc/backend/chat/tests/evals/test_reset_evals.pysrc/backend/chat/tests/evals/test_storage.pysrc/backend/chat/tests/evals/test_tool_selection.pysrc/backend/chat/tests/evals/test_tool_stub_responses.pysrc/backend/conversations/settings.pysrc/backend/pyproject.toml
🚧 Files skipped from review as they are similar to previous changes (33)
- src/backend/chat/evals/init.py
- .codespellrc
- src/backend/chat/evals/evaluators/span.py
- src/backend/chat/evals/configs/init.py
- src/backend/.pylintrc
- src/backend/chat/evals/datasets/incertitude.yaml
- src/backend/chat/evals/baselines/main.json
- src/backend/chat/evals/configs/url_hallucination.py
- .gitignore
- src/backend/chat/evals/configs/tool_selection.py
- src/backend/chat/management/commands/generate_eval_dashboard.py
- src/backend/chat/tests/evals/test_tool_selection.py
- src/backend/chat/tests/evals/test_configs.py
- src/backend/chat/evals/evaluators/init.py
- src/backend/chat/evals/tool_output.py
- Makefile
- src/backend/chat/tests/evals/test_production_agent.py
- src/backend/chat/management/commands/create_eval_baseline.py
- src/backend/chat/evals/report_builder.py
- src/backend/chat/tests/evals/test_dashboard.py
- src/backend/chat/evals/configs/base.py
- src/backend/chat/evals/datasets/tool_selection.yaml
- src/backend/chat/management/commands/reset_evals.py
- src/backend/chat/evals/configs/faithfulness_rag.py
- src/backend/chat/evals/datasets/url_hallucination.yaml
- src/backend/chat/evals/datasets/faithfulness_rag.yaml
- src/backend/chat/tests/evals/test_report_builder.py
- src/backend/chat/evals/production_agent.py
- src/backend/chat/management/commands/compare_evals.py
- src/backend/chat/tests/evals/test_compare.py
- src/backend/chat/evals/baselines/main_run.json
- src/backend/chat/evals/README.md
- src/backend/chat/evals/compare.py
| if options["save"] and options["case"]: | ||
| raise CommandError( | ||
| "--save cannot be combined with --case: a partial run would register " | ||
| "every other case as a coverage gap (= regression) when compared " | ||
| "against the baseline." | ||
| ) |
There was a problem hiding this comment.
🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win
Reject dataset-filtered saved runs too.
--dataset also creates a partial record: Lines 106 and 121-128 persist only that dataset, so comparison against a full baseline will classify every omitted dataset as a coverage regression. Apply the same --save restriction to --dataset, or make comparison explicitly support scoped baselines.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@src/backend/chat/management/commands/run_evals.py` around lines 86 - 91,
Extend the validation around the save option in the run-evals command so --save
cannot be combined with either --case or --dataset. Preserve the existing
CommandError behavior and message context, updating it to identify both filters
as unsupported for saved partial runs.
| use_llm_judge = not options["no_llm_judge"] | ||
| self._configure_judge(use_llm_judge) | ||
|
|
||
| configs = [REGISTRY[options["dataset"]]] if options["dataset"] else list(REGISTRY.values()) |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win
Require --dataset when filtering by --case.
The selected case is passed to every registered dataset. If it exists in only one dataset, _load_dataset aborts on the first other dataset instead of running the requested case. Reject --case without --dataset with a clear CommandError.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@src/backend/chat/management/commands/run_evals.py` at line 106, Update the
argument validation in the eval command before the REGISTRY selection so
providing --case without --dataset raises a clear CommandError. Keep the
existing configs selection unchanged when --dataset is supplied or when running
without a case filter.
205e2bd to
18e7ee7
Compare
- run_evals management command and Make targets - datasets, evaluators, save/compare/baseline flow - HTML dashboard
18e7ee7 to
f63a865
Compare
|
There was a problem hiding this comment.
Actionable comments posted: 10
♻️ Duplicate comments (5)
src/backend/chat/evals/storage.py (2)
36-37: 🩺 Stability & Availability | 🔴 Critical | ⚡ Quick winPython 2
exceptsyntax — module fails to import.
except subprocess.CalledProcessError, FileNotFoundError:is a hardSyntaxErrorin Python 3, sostorage.py(and every command importing it) cannot load. Same at Line 52. This was previously reported as fixed, but the supplied code still has it.🐛 Proposed fix (apply to both lines 36 and 52)
- except subprocess.CalledProcessError, FileNotFoundError: + except (subprocess.CalledProcessError, FileNotFoundError): return None🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/backend/chat/evals/storage.py` around lines 36 - 37, Update both exception handlers in storage.py to use Python 3 tuple syntax for catching subprocess.CalledProcessError and FileNotFoundError, preserving their existing return behavior so the module imports successfully.
93-97: 🗄️ Data Integrity & Integration | 🟡 Minor | ⚡ Quick winRun IDs still collide at second precision.
Two runs in the same second on the same commit produce the same
run_id, sosave_runoverwrites the earlier artifact and the index entry is replaced. Add sub-second precision or a short random suffix.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/backend/chat/evals/storage.py` around lines 93 - 97, Update make_run_id so generated IDs remain unique for runs sharing the same second and Git commit, by adding sub-second timestamp precision or a short random suffix while preserving the existing timestamp and git_short components.src/backend/chat/management/commands/reset_evals.py (1)
49-55: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick winDry-run still reports
index.jsonas deleted.
reset_eval_artifactsexcludesindex.jsonfrom unlinking (storage.pyLine 313) and only clears its contents in place, and Line 55 also appends it even when it doesn't exist.🩹 Proposed fix
- targets.append(str((RUNS_DIR / "index.json").relative_to(RUNS_DIR.parent))) + index_path = RUNS_DIR / "index.json" + if index_path.exists(): + targets.append(f"{index_path.relative_to(RUNS_DIR.parent)} (reset, not deleted)")🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/backend/chat/management/commands/reset_evals.py` around lines 49 - 55, Update the target collection in reset_eval_artifacts so dry-run output matches the actual cleanup behavior: exclude index.json from the deletion targets and only include it when it exists and is being cleared in place. Preserve reporting of other matching run JSON files.src/backend/chat/management/commands/run_evals.py (2)
86-91: 🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick winIncomplete flag-combination validation in
handle. Only--save+--caseis rejected; the other two invalid combinations involving--datasetslip through and either persist a partial run or abort a legitimate single-case run.
src/backend/chat/management/commands/run_evals.py#L86-L91: extend the guard to also reject--savewith--dataset(partial record → every omitted dataset scores as a coverage gap against a full baseline), and to reject--casewithout--dataset.src/backend/chat/management/commands/run_evals.py#L106: with that guard in place,configsis a single dataset whenever--caseis set, so_load_datasetno longer aborts on an unrelated dataset.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/backend/chat/management/commands/run_evals.py` around lines 86 - 91, The handle validation guard must reject all invalid combinations: --save with --case, --save with --dataset, and --case without --dataset. Update the guard around the existing CommandError, and ensure the configs logic near line 106 uses only the single dataset when --case is set so _load_dataset does not process unrelated datasets.
106-106: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win
--casewithout--datasetaborts instead of running the case.The case name is applied to every registered dataset, and
_load_datasetraisesCommandErrorfor the first dataset that lacks it. Require--datasetwhen--caseis given.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/backend/chat/management/commands/run_evals.py` at line 106, Update the option validation in the run-evals command before the configs assignment to reject --case unless --dataset is also provided, raising the command’s existing CommandError with a clear message. Preserve the current behavior for dataset-only and case-plus-dataset invocations, and prevent the case from being applied across all REGISTRY entries.
🧹 Nitpick comments (8)
src/backend/chat/evals/dashboard/template.html (1)
716-731: 📐 Maintainability & Code Quality | 🔵 TrivialNested ternaries in
summaryRow/deltaHtml.SonarCloud flags this construct for readability; consider extracting to an if/else chain or small helper for clarity.
♻️ Suggested refactor
- const deltaHtml = pp == null - ? '<span class="muted">—</span>' - : pp === 0 - ? '<span class="muted">=</span>' - : `<span class="${pp > 0 ? "delta-pos" : "delta-neg"}">${pp > 0 ? "+" : ""}${pp}pp</span>`; + let deltaHtml = '<span class="muted">—</span>'; + if (pp === 0) { + deltaHtml = '<span class="muted">=</span>'; + } else if (pp != null) { + deltaHtml = `<span class="${pp > 0 ? "delta-pos" : "delta-neg"}">${pp > 0 ? "+" : ""}${pp}pp</span>`; + }🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/backend/chat/evals/dashboard/template.html` around lines 716 - 731, Refactor the nested ternary expressions in summaryRow, especially the deltaHtml and fillB assignments, into clear if/else logic or a small local helper. Preserve the existing null, zero, positive, and negative rate-difference outputs and class assignments without changing behavior.Source: Linters/SAST tools
.codespellrc (1)
14-14: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low valueConsider narrowing the skip to the data directories.
The whole
chat/evalspackage is excluded, so Python sources and docstrings there lose spellchecking too. Skipping only./src/backend/chat/evals/datasetsand./src/backend/chat/evals/baselineskeeps coverage on code while ignoring the French fixtures.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In @.codespellrc at line 14, Update the codespell exclusions to remove the broad ./src/backend/chat/evals entry and instead skip only ./src/backend/chat/evals/datasets and ./src/backend/chat/evals/baselines, preserving spellchecking for the package’s Python code and docstrings.src/backend/.pylintrc (1)
22-22: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low valueDocument why parallel Pylint was disabled.
Forcing
jobs=1slows lint across the whole backend, and the change is unrelated to the eval feature. If this works around a parallel-mode issue (e.g.pylint_django/similarity checker instability with the new modules), add an inline comment; otherwise consider reverting tojobs=0.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/backend/.pylintrc` at line 22, Update the jobs setting in the Pylint configuration to use parallel execution unless jobs=1 is required for a known issue; if retaining jobs=1, add an inline comment documenting the specific parallel-mode failure or dependency instability it avoids.src/backend/chat/evals/evaluators/span.py (1)
21-22: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick winGuard against missing span-tree instrumentation.
ctx.span_treeraisesSpanTreeRecordingErrorwhen the tree was not captured, so a single uninstrumented case turns eachHasNoMatchingSpandid_not_call_*assertion into an evaluator error instead of a pass/fail result. Catch that exception and return a failed span-negative result before calling.any(...).🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/backend/chat/evals/evaluators/span.py` around lines 21 - 22, Update HasNoMatchingSpan.evaluate to catch SpanTreeRecordingError when accessing ctx.span_tree and return False before invoking any(self.query). Preserve the existing negated any(...) behavior when span-tree instrumentation is available.src/backend/chat/tests/evals/test_configs.py (1)
40-54: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winTest duplicates
run_evals.py's dataset-loading logic instead of exercising it directly.This re-implements
_load_dataset'scustom_evaluator_typesassembly andDataset.from_textcall rather than calling shared logic, so the test can stay green even if the actualrun_evalspath diverges — undermining its own stated purpose of validating "the run_evals path".Consider extracting the loading logic (e.g.
EvalConfig.build_dataset(case_name=None)inconfigs/base.py) and having bothrun_evals.pyand this test call it, so a regression in the real loading path is guaranteed to fail here too.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/backend/chat/tests/evals/test_configs.py` around lines 40 - 54, Extract the shared dataset-construction logic from run_evals.py into an EvalConfig method such as build_dataset(case_name=None) in configs/base.py, including evaluator-type assembly and Dataset.from_text setup. Update run_evals.py and test_all_datasets_parse_after_config_strip to call this method, while preserving each caller’s existing name and case-selection behavior.src/backend/chat/evals/production_agent.py (1)
102-174: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winConsider consolidating eval-vs-production wiring to reduce drift risk.
build_production_agent_servicereimplements theweb_search_enableddefault logic (_is_web_search_enabled and _is_smart_search_enabled), andensure_web_search_registeredreimplements its ownonly_if_web_search_enabledprepare-gate and tool registration — both mirroring behavior that appears to live inAIAgentService._setup_web_search_tool/_run_agent. Since the whole point of this module is to test the real production wiring, any future change to that upstream logic won't automatically propagate here, silently making evals test stale behavior.Consider exposing a small shared helper (e.g., a method or module-level function used by both
AIAgentServiceand this eval harness) for the enablement decision and the stub-registration prepare-gate, so eval wiring can't drift from production wiring.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/backend/chat/evals/production_agent.py` around lines 102 - 174, Consolidate web-search enablement and registration logic shared by AIAgentService and the eval harness into reusable helpers, then update build_production_agent_service and ensure_web_search_registered to call them instead of duplicating the _is_web_search_enabled/_is_smart_search_enabled decision and only_if_web_search_enabled gate/tool setup. Keep production and eval behavior identical while ensuring future changes to the shared wiring propagate automatically.src/backend/chat/evals/tool_stub_responses.py (1)
79-86: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winConsider rejecting unknown keys in dataset
tool_outputJSON.
ToolStubResponsesuses pydantic's defaultextra="ignore". A typo in a dataset'stool_outputJSON key (e.g. misspellingweb_search) would silently fall back to defaults instead of failing, making dataset authoring mistakes hard to catch.♻️ Proposed fix
-class ToolStubResponses(BaseModel): +class ToolStubResponses(BaseModel): """Per-case simulated tool payloads (JSON in dataset ``tool_output``).""" + + model_config = ConfigDict(extra="forbid") web_search: dict[str, Any] | None = None🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/backend/chat/evals/tool_stub_responses.py` around lines 79 - 86, Configure the ToolStubResponses Pydantic model to reject unknown fields in dataset tool_output JSON instead of silently ignoring them. Update the model configuration near the existing fields while preserving all declared fields and their defaults.src/backend/chat/management/commands/create_eval_baseline.py (1)
30-39: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low valueRedundant
resolve_runcall.
set_baselinealready callsresolve_runinternally and raises the sameFileNotFoundError, so Line 32 just re-reads and re-parses the run file.♻️ Proposed cleanup
try: - resolve_run(options["run"]) baseline = set_baseline((drop
resolve_runfrom the import as well)🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/backend/chat/management/commands/create_eval_baseline.py` around lines 30 - 39, Remove the redundant resolve_run(options["run"]) invocation from handle and drop resolve_run from the imports. Keep set_baseline as the sole run-resolution path while preserving the existing FileNotFoundError-to-CommandError handling.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@src/backend/chat/evals/baselines/main_run.json`:
- Around line 5-10: Regenerate the baseline metadata in main_run.json from a
clean checkout of the finalized merge commit, ensuring the git dirty flag is
false and the recorded commit, short commit, and branch reference the
reproducible merge state rather than the dirty feature-branch working tree.
In `@src/backend/chat/evals/compare.py`:
- Around line 139-271: Update _compare_case to create a CaseChange when a case
exists only in after_cases, instead of returning None. Add a _new_case_change
helper mirroring _missing_after_case_change, mark the result as a coverage gap
with a distinct "new_case" kind, and preserve the after-case metrics and
reasons. Update template.html’s comparisonRows() to iterate the union of before
and after case names so new cases appear in reports.
In `@src/backend/chat/evals/configs/faithfulness_rag.py`:
- Around line 71-87: Move the production_agent_deps(service) call and
web_search_enabled assignment from make_faithfulness_rag_task_fn into run_agent
so each eval case receives fresh per-call dependencies. Keep the agent and
tool-stub setup unchanged, and ensure run_agent passes the newly created deps to
agent.run.
In `@src/backend/chat/evals/datasets/tool_selection.yaml`:
- Around line 111-123: Update the web_search tool_output fixture in the dataset
entry to remove the stale François Bayrou officeholder claim and use a
timeline-neutral placeholder consistent with the mid-2026 framing. Preserve the
existing JSON structure, URL, title, and span-oriented evaluation behavior while
avoiding a specific current Prime Minister or date that requires future factual
updates.
In `@src/backend/chat/evals/evaluators/url_regex.py`:
- Around line 23-24: Update evaluate in the URL regex evaluator to validate
ctx.output with an isinstance(..., str) guard before calling _extract_urls,
matching the handling of other inputs. Ensure non-string output follows the
evaluator’s existing non-match/reasoning path instead of reaching re.findall and
raising TypeError.
In `@src/backend/chat/evals/production_agent.py`:
- Around line 71-99: Reset or recreate the eval conversation before each eval
case/run so _get_eval_session_conversation does not reuse prior
pydantic_messages; update the surrounding eval setup or run flow while
preserving the shared eval user behavior. Also synchronize the module-global
initialization in _get_eval_session_user and _get_eval_session_conversation,
including their get_or_create paths, so parallel execution cannot duplicate
creation.
In `@src/backend/chat/management/commands/generate_eval_dashboard.py`:
- Around line 16-25: Update the path formatting in handle around
output_path.relative_to(settings.BASE_DIR) to catch cases where output_path is
outside settings.BASE_DIR, preserving the successful dashboard generation and
falling back to displaying output_path’s absolute path. Keep the existing
host-side display path when relative_to succeeds.
In `@src/backend/chat/providers/albert_models.py`:
- Around line 78-108: Reduce cognitive complexity in _validate_completion by
extracting the tool-call type normalization loop and its custom-payload
condition into a small helper. Have _validate_completion invoke the helper for
each valid choice while preserving the existing handling of malformed choices,
tool calls, and genuine custom tool calls.
In `@src/backend/chat/tests/evals/test_compare.py`:
- Around line 47-49: Update the comparison fixture and assertions around
comparison.coverage_gaps and comparison.regressions so cases missing from the
after run are represented with after_passed unknown and excluded from
regressions. Preserve coverage_gaps for those cases, and adjust the expected
regression count to verify previously passing missing cases are not
double-counted.
In `@src/backend/conversations/settings.py`:
- Around line 745-748: Remove the hardcoded “Mistral Medium 2508” identity from
the default prompt in the assistant instructions, while preserving the
deployment-neutral description and the existing prohibition against presenting
assumptions as facts. Ensure model-specific identity is obtained from the
resolved LLM configuration or the self_documentation tool so the about_model
path reflects the active deployment.
---
Duplicate comments:
In `@src/backend/chat/evals/storage.py`:
- Around line 36-37: Update both exception handlers in storage.py to use Python
3 tuple syntax for catching subprocess.CalledProcessError and FileNotFoundError,
preserving their existing return behavior so the module imports successfully.
- Around line 93-97: Update make_run_id so generated IDs remain unique for runs
sharing the same second and Git commit, by adding sub-second timestamp precision
or a short random suffix while preserving the existing timestamp and git_short
components.
In `@src/backend/chat/management/commands/reset_evals.py`:
- Around line 49-55: Update the target collection in reset_eval_artifacts so
dry-run output matches the actual cleanup behavior: exclude index.json from the
deletion targets and only include it when it exists and is being cleared in
place. Preserve reporting of other matching run JSON files.
In `@src/backend/chat/management/commands/run_evals.py`:
- Around line 86-91: The handle validation guard must reject all invalid
combinations: --save with --case, --save with --dataset, and --case without
--dataset. Update the guard around the existing CommandError, and ensure the
configs logic near line 106 uses only the single dataset when --case is set so
_load_dataset does not process unrelated datasets.
- Line 106: Update the option validation in the run-evals command before the
configs assignment to reject --case unless --dataset is also provided, raising
the command’s existing CommandError with a clear message. Preserve the current
behavior for dataset-only and case-plus-dataset invocations, and prevent the
case from being applied across all REGISTRY entries.
---
Nitpick comments:
In @.codespellrc:
- Line 14: Update the codespell exclusions to remove the broad
./src/backend/chat/evals entry and instead skip only
./src/backend/chat/evals/datasets and ./src/backend/chat/evals/baselines,
preserving spellchecking for the package’s Python code and docstrings.
In `@src/backend/.pylintrc`:
- Line 22: Update the jobs setting in the Pylint configuration to use parallel
execution unless jobs=1 is required for a known issue; if retaining jobs=1, add
an inline comment documenting the specific parallel-mode failure or dependency
instability it avoids.
In `@src/backend/chat/evals/dashboard/template.html`:
- Around line 716-731: Refactor the nested ternary expressions in summaryRow,
especially the deltaHtml and fillB assignments, into clear if/else logic or a
small local helper. Preserve the existing null, zero, positive, and negative
rate-difference outputs and class assignments without changing behavior.
In `@src/backend/chat/evals/evaluators/span.py`:
- Around line 21-22: Update HasNoMatchingSpan.evaluate to catch
SpanTreeRecordingError when accessing ctx.span_tree and return False before
invoking any(self.query). Preserve the existing negated any(...) behavior when
span-tree instrumentation is available.
In `@src/backend/chat/evals/production_agent.py`:
- Around line 102-174: Consolidate web-search enablement and registration logic
shared by AIAgentService and the eval harness into reusable helpers, then update
build_production_agent_service and ensure_web_search_registered to call them
instead of duplicating the _is_web_search_enabled/_is_smart_search_enabled
decision and only_if_web_search_enabled gate/tool setup. Keep production and
eval behavior identical while ensuring future changes to the shared wiring
propagate automatically.
In `@src/backend/chat/evals/tool_stub_responses.py`:
- Around line 79-86: Configure the ToolStubResponses Pydantic model to reject
unknown fields in dataset tool_output JSON instead of silently ignoring them.
Update the model configuration near the existing fields while preserving all
declared fields and their defaults.
In `@src/backend/chat/management/commands/create_eval_baseline.py`:
- Around line 30-39: Remove the redundant resolve_run(options["run"]) invocation
from handle and drop resolve_run from the imports. Keep set_baseline as the sole
run-resolution path while preserving the existing
FileNotFoundError-to-CommandError handling.
In `@src/backend/chat/tests/evals/test_configs.py`:
- Around line 40-54: Extract the shared dataset-construction logic from
run_evals.py into an EvalConfig method such as build_dataset(case_name=None) in
configs/base.py, including evaluator-type assembly and Dataset.from_text setup.
Update run_evals.py and test_all_datasets_parse_after_config_strip to call this
method, while preserving each caller’s existing name and case-selection
behavior.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Repository UI
Review profile: CHILL
Plan: Pro Plus
Run ID: 1f8e3555-16ea-47ec-b837-9a97078d3477
⛔ Files ignored due to path filters (1)
src/backend/uv.lockis excluded by!**/*.lock
📒 Files selected for processing (54)
.codespellrc.gitignoreCHANGELOG.mdMakefilesrc/backend/.pylintrcsrc/backend/chat/agents/base.pysrc/backend/chat/evals/README.mdsrc/backend/chat/evals/__init__.pysrc/backend/chat/evals/baselines/.gitkeepsrc/backend/chat/evals/baselines/main.jsonsrc/backend/chat/evals/baselines/main_run.jsonsrc/backend/chat/evals/compare.pysrc/backend/chat/evals/configs/__init__.pysrc/backend/chat/evals/configs/base.pysrc/backend/chat/evals/configs/faithfulness_rag.pysrc/backend/chat/evals/configs/incertitude.pysrc/backend/chat/evals/configs/tool_selection.pysrc/backend/chat/evals/configs/url_hallucination.pysrc/backend/chat/evals/dashboard.pysrc/backend/chat/evals/dashboard/.gitkeepsrc/backend/chat/evals/dashboard/template.htmlsrc/backend/chat/evals/datasets/faithfulness_rag.yamlsrc/backend/chat/evals/datasets/incertitude.yamlsrc/backend/chat/evals/datasets/tool_selection.yamlsrc/backend/chat/evals/datasets/url_hallucination.yamlsrc/backend/chat/evals/evaluators/__init__.pysrc/backend/chat/evals/evaluators/span.pysrc/backend/chat/evals/evaluators/url_regex.pysrc/backend/chat/evals/production_agent.pysrc/backend/chat/evals/report_builder.pysrc/backend/chat/evals/runs/.gitkeepsrc/backend/chat/evals/storage.pysrc/backend/chat/evals/tool_output.pysrc/backend/chat/evals/tool_stub_responses.pysrc/backend/chat/management/commands/compare_evals.pysrc/backend/chat/management/commands/create_eval_baseline.pysrc/backend/chat/management/commands/generate_eval_dashboard.pysrc/backend/chat/management/commands/reset_evals.pysrc/backend/chat/management/commands/run_evals.pysrc/backend/chat/providers/albert_models.pysrc/backend/chat/tests/agents/test_albert_models.pysrc/backend/chat/tests/evals/__init__.pysrc/backend/chat/tests/evals/test_compare.pysrc/backend/chat/tests/evals/test_configs.pysrc/backend/chat/tests/evals/test_dashboard.pysrc/backend/chat/tests/evals/test_production_agent.pysrc/backend/chat/tests/evals/test_report_builder.pysrc/backend/chat/tests/evals/test_reset_evals.pysrc/backend/chat/tests/evals/test_storage.pysrc/backend/chat/tests/evals/test_tool_selection.pysrc/backend/chat/tests/evals/test_tool_stub_responses.pysrc/backend/chat/tools/descriptions.pysrc/backend/conversations/settings.pysrc/backend/pyproject.toml
🚧 Files skipped from review as they are similar to previous changes (5)
- CHANGELOG.md
- src/backend/chat/evals/configs/init.py
- .gitignore
- src/backend/chat/tests/evals/test_report_builder.py
- src/backend/chat/evals/README.md
| "git": { | ||
| "commit": "205e2bde3163636e9db9fa288e7f016e3fe43975", | ||
| "commit_short": "205e2bd", | ||
| "branch": "maxenceh/setup-eval-llm", | ||
| "dirty": true | ||
| }, |
There was a problem hiding this comment.
🗄️ Data Integrity & Integration | 🟡 Minor | ⚡ Quick win
Baseline captured from a dirty working tree.
"dirty": true on a feature branch means this committed reference can't be reproduced from 205e2bd, which weakens future regression comparisons. Consider regenerating the baseline from a clean checkout of the merge commit once this lands.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@src/backend/chat/evals/baselines/main_run.json` around lines 5 - 10,
Regenerate the baseline metadata in main_run.json from a clean checkout of the
finalized merge commit, ensuring the git dirty flag is false and the recorded
commit, short commit, and branch reference the reproducible merge state rather
than the dirty feature-branch working tree.
| def _missing_after_case_change( | ||
| *, | ||
| dataset_name: str, | ||
| before_case: dict[str, Any], | ||
| ) -> CaseChange: | ||
| """Build a CaseChange for a case that is absent from the after run.""" | ||
| return CaseChange( | ||
| dataset=dataset_name, | ||
| case_name=before_case["name"], | ||
| before_passed=before_case["passed"], | ||
| after_passed=False, | ||
| before_pass_rate=before_case["pass_rate"], | ||
| after_pass_rate=0.0, | ||
| before_avg_scores=before_case["avg_scores"], | ||
| after_avg_scores={}, | ||
| reasons={"_coverage": "missing from after run"}, | ||
| coverage_gap=True, | ||
| ) | ||
|
|
||
|
|
||
| def _dataset_comparison_missing_after( | ||
| dataset_name: str, | ||
| before_dataset: dict[str, Any], | ||
| ) -> DatasetComparison: | ||
| """Build a dataset comparison when the after run has no results for it.""" | ||
| case_changes = [ | ||
| _missing_after_case_change(dataset_name=dataset_name, before_case=before_case) | ||
| for before_case in sorted(before_dataset.get("cases", []), key=lambda case: case["name"]) | ||
| ] | ||
| return DatasetComparison( | ||
| dataset=dataset_name, | ||
| before_pass_rate=before_dataset["pass_rate"], | ||
| after_pass_rate=0.0, | ||
| before_pass_rate_avg_repeats=before_dataset["pass_rate_avg_repeats"], | ||
| after_pass_rate_avg_repeats=0.0, | ||
| dataset_hash_match=False, | ||
| case_changes=case_changes, | ||
| ) | ||
|
|
||
|
|
||
| def _cases_unchanged( | ||
| before_case: dict[str, Any], | ||
| after_case: dict[str, Any], | ||
| ) -> bool: | ||
| """Return whether pass status, pass rate, and scores are identical.""" | ||
| return ( | ||
| before_case["passed"] == after_case["passed"] | ||
| and before_case["pass_rate"] == after_case["pass_rate"] | ||
| and before_case["avg_scores"] == after_case["avg_scores"] | ||
| ) | ||
|
|
||
|
|
||
| def _case_change_for_pair( | ||
| *, | ||
| dataset_name: str, | ||
| case_name: str, | ||
| before_case: dict[str, Any], | ||
| after_case: dict[str, Any], | ||
| ) -> CaseChange: | ||
| """Build a CaseChange for a case present in both runs with differing results.""" | ||
| return CaseChange( | ||
| dataset=dataset_name, | ||
| case_name=case_name, | ||
| before_passed=before_case["passed"], | ||
| after_passed=after_case["passed"], | ||
| before_pass_rate=before_case["pass_rate"], | ||
| after_pass_rate=after_case["pass_rate"], | ||
| before_avg_scores=before_case["avg_scores"], | ||
| after_avg_scores=after_case["avg_scores"], | ||
| reasons=after_case.get("reasons", {}), | ||
| ) | ||
|
|
||
|
|
||
| def _compare_case( | ||
| *, | ||
| dataset_name: str, | ||
| case_name: str, | ||
| before_cases: dict[str, dict[str, Any]], | ||
| after_cases: dict[str, dict[str, Any]], | ||
| ) -> CaseChange | None: | ||
| """Compare one case across runs; return a CaseChange when a delta exists.""" | ||
| before_case = before_cases.get(case_name) | ||
| after_case = after_cases.get(case_name) | ||
| if after_case is None: | ||
| if before_case is not None: | ||
| return _missing_after_case_change( | ||
| dataset_name=dataset_name, | ||
| before_case=before_case, | ||
| ) | ||
| return None | ||
| if before_case is None or _cases_unchanged(before_case, after_case): | ||
| return None | ||
| return _case_change_for_pair( | ||
| dataset_name=dataset_name, | ||
| case_name=case_name, | ||
| before_case=before_case, | ||
| after_case=after_case, | ||
| ) | ||
|
|
||
|
|
||
| def _compare_datasets( | ||
| *, | ||
| dataset_name: str, | ||
| before_dataset: dict[str, Any], | ||
| after_dataset: dict[str, Any], | ||
| dataset_hash_match: bool, | ||
| ) -> DatasetComparison: | ||
| """Compare one dataset present in both runs.""" | ||
| before_cases = _case_map(before_dataset) | ||
| after_cases = _case_map(after_dataset) | ||
| case_changes = [ | ||
| change | ||
| for case_name in sorted(set(before_cases) | set(after_cases)) | ||
| if ( | ||
| change := _compare_case( | ||
| dataset_name=dataset_name, | ||
| case_name=case_name, | ||
| before_cases=before_cases, | ||
| after_cases=after_cases, | ||
| ) | ||
| ) | ||
| is not None | ||
| ] | ||
|
|
||
| return DatasetComparison( | ||
| dataset=dataset_name, | ||
| before_pass_rate=before_dataset["pass_rate"], | ||
| after_pass_rate=after_dataset["pass_rate"], | ||
| before_pass_rate_avg_repeats=before_dataset["pass_rate_avg_repeats"], | ||
| after_pass_rate_avg_repeats=after_dataset["pass_rate_avg_repeats"], | ||
| dataset_hash_match=dataset_hash_match, | ||
| case_changes=case_changes, | ||
| ) |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟠 Major | ⚡ Quick win
New cases (only in the after run) are silently dropped from the comparison.
_compare_case handles the "missing from after" direction explicitly (_missing_after_case_change creates a coverage_gap CaseChange), but the symmetric case — a case that exists only in after_case because before_case is None — hits if before_case is None or _cases_unchanged(...): return None and produces no CaseChange at all. New cases added to a dataset therefore never show up in case_changes, regressions, improvements, or coverage_gaps, even if the new case fails. The dataset's aggregate pass_rate/cases_total still reflect it, and the raw run-detail view still lists it, but the diff/regression report gives no indication a case is new or why the numbers shifted.
Since template.html's comparisonRows() also only iterates bd.cases (the before dataset), fully surfacing this would additionally require unioning case names there — worth keeping in mind if this is addressed.
💡 Illustrative fix direction
- if before_case is None or _cases_unchanged(before_case, after_case):
+ if before_case is None:
+ return _new_case_change(dataset_name=dataset_name, after_case=after_case)
+ if _cases_unchanged(before_case, after_case):
return None(with a _new_case_change helper mirroring _missing_after_case_change, and a "new_case" branch added to CaseChange.kind.)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@src/backend/chat/evals/compare.py` around lines 139 - 271, Update
_compare_case to create a CaseChange when a case exists only in after_cases,
instead of returning None. Add a _new_case_change helper mirroring
_missing_after_case_change, mark the result as a coverage gap with a distinct
"new_case" kind, and preserve the after-case metrics and reasons. Update
template.html’s comparisonRows() to iterate the union of before and after case
names so new cases appear in reports.
| def make_faithfulness_rag_task_fn(model_hrid: str): | ||
| """Build the task function: production wiring + stub RAG implementation.""" | ||
| service = _build_faithfulness_rag_service(model_hrid) | ||
| stub_document_search_rag(service, _stub_document_search_rag) | ||
| agent = service.conversation_agent | ||
| deps = production_agent_deps(service) | ||
| deps.web_search_enabled = False | ||
|
|
||
| async def run_agent(inputs: EvalInputs) -> str: | ||
| stubs = ToolStubResponses(document_search_rag=inputs.tool_output or _NO_PASSAGES) | ||
| token = set_current_tool_stubs(stubs) | ||
| try: | ||
| return (await agent.run(inputs.user_message, deps=deps)).output | ||
| finally: | ||
| reset_current_tool_stubs(token) | ||
|
|
||
| return run_agent |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "Files:"
git ls-files | rg 'faithfulness_rag\.py|tool_selection\.py|production_agent\.py|conversation_agent' || true
echo
echo "faithfulness_rag outline:"
ast-grep outline src/backend/chat/evals/configs/faithfulness_rag.py --view expanded || true
echo
echo "faithfulness_rag relevant lines:"
sed -n '1,140p' src/backend/chat/evals/configs/faithfulness_rag.py | cat -n
echo
echo "tool_selection relevant patterns:"
fd -i 'tool_selection\.py' . | while read -r f; do
echo "--- $f"
wc -l "$f"
rg -n "production_agent_deps|make_.*task_fn|run_agent|web_search_enabled" "$f" -C 3
doneRepository: suitenumerique/conversations
Length of output: 6788
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "Search production_agent_deps definitions/usages:"
rg -n "def production_agent_deps|production_agent_deps\(|class ProductionAgentDeps|ProductionAgentDeps|conversation_agent\.run|def run\(" src -g '*.py'
echo
echo "Candidate files:"
git ls-files '*.py' | rg 'chat/|evals/|agent|deps' | head -200
echo
echo "Inspect source snippets from files containing production_agent_deps:"
rg -l "production_agent_deps" src/backend/chat evals 2>/dev/null || true
for f in $(rg -l "production_agent_deps" src/backend/chat src/backend/evals 2>/dev/null || true); do
echo "--- $f"
rg -n "production_agent_deps|class .*Deps|`@dataclass`|dataclass|web_search_enabled|conversation_history|history|tool_output" "$f" -C 5
doneRepository: suitenumerique/conversations
Length of output: 19321
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "production_agent.py outline/relevant:"
ast-grep outline src/backend/chat/evals/production_agent.py --view expanded || true
sed -n '1,220p' src/backend/chat/evals/production_agent.py | cat -n
echo
echo "chat clients/pydantic_ai/agents relevant deps/run definitions:"
awk 'NR>=1 && NR<=320 { print NR ": " $0 }' src/backend/chat/agents/conversation.py | cat -n
echo
rg -n "_context_deps|ContextDeps|class ContextDeps|web_search_enabled|run\(|result\.messages|new_messages|messages|history|conversation_history|run_sync|deps" src/backend/chat/agents src/backend/chat/clients src/backend/chat/evals/production_agent.py -C 4
echo
echo "Pydantic AI package availability/version:"
python3 - <<'PY'
import importlib.util
for name in ["pydantic_ai", "pydantic_ai.main"]:
spec = importlib.util.find_spec(name)
print(name, "FOUND" if spec else "NO_SPEC", spec.origin if spec else "")
try:
import pydantic_ai
print("pydantic_ai.__version__", getattr(pydantic_ai, "__version__", "<no version>"))
import inspect
print("run signature", inspect.signature(pydantic_ai.Agent.run))
print("main Agent.py file", inspect.getfile(pydantic_ai.Agent))
except Exception as e:
print("pydantic_ai import error:", type(e).__name__, e)
PYRepository: suitenumerique/conversations
Length of output: 50384
🌐 Web query:
Pydantic AI Agent.run deps mutable context concurrency thread safety run_sync docs
💡 Result:
In PydanticAI, agent runs are safe for concurrency [1]. You can concurrently execute agent runs (e.g., using asyncio.TaskGroup) without issues [1]. Regarding dependencies, context, and concurrency: Dependencies and Context Dependencies are passed through the RunContext object, which is accessed as the first parameter of system prompt functions, tools, and output validators [2][3]. These functions execute within the asynchronous context of an agent run [2][4]. If a dependency function is synchronous, PydanticAI runs it in a thread pool using asyncio.loop.run_in_executor [2][4]. Mutable Context While agent runs are concurrency-safe, developers must manage state carefully. If your deps object contains mutable data shared across concurrent agent runs, you must ensure that your own code handles thread safety (e.g., via locks), as PydanticAI does not automatically synchronize access to custom dependency objects [2][1]. Run vs. Run_sync Agents always execute within an asynchronous context [2][4]. run_sync is a synchronous wrapper around the run method, which internally executes loop.run_until_complete(self.run()) [5]. Consequently, whether you use synchronous or asynchronous dependencies is independent of whether you invoke the agent via run or run_sync [2][4]. Concurrency Limiting You can control the volume of concurrent operations using the max_concurrency parameter on the Agent constructor [6][5][7]. This supports: - An integer: A simple limit on concurrent operations [8][7]. - A ConcurrencyLimit: Advanced configuration providing optional backpressure (e.g., queue size limits) [8][5]. - A ConcurrencyLimiter: An object to share limits across multiple agents or models [8][7]. If the concurrency limit is reached, calls to run() or iter() will wait; if a max_queued limit is configured and exceeded, a ConcurrencyLimitExceeded exception is raised [6][5].
Citations:
- 1: Is Agent.run and other call safe for concurrency? pydantic/pydantic-ai#3241
- 2: https://pydantic.dev/docs/ai/core-concepts/dependencies/
- 3: https://github.com/pydantic/pydantic-ai/blob/7f5214c6/docs/dependencies.md
- 4: https://pydantic.dev/docs/ai/core-concepts/dependencies
- 5: https://github.com/pydantic/pydantic-ai/blob/main/docs/agent.md
- 6: https://pydantic.dev/docs/ai/core-concepts/agent/
- 7: https://ai.pydantic.dev/api/agent/index.md
- 8: https://pydantic.dev/docs/ai/api/pydantic-ai/concurrency/
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "Inspect ChatConversation / service hooks and dependency fields:"
rg -n "class ChatConversation|model_post_init|last_message_at|updated_at|messaging|updated_by|ConversationAgent|add_agent_hooks|last_user_message_at|user_is_typing|send_message|run_agent_async|run_agent|_run_agent|context_deps" src/backend/chat clients/agents models -g '*.py' -C 4
echo
echo "Focused files containing relevant hooks/deps:"
rg -l "model_post_init|user_is_typing|last_user_message_at|add_agent_hooks|ConversationAgent|AIAgentService|_context_deps" src/backend/chat src/backend/agents 2>/dev/null || trueRepository: suitenumerique/conversations
Length of output: 50385
Build deps inside each case run.
production_agent_deps(service) returns the shared mutable AIAgentService._context_deps, and a new ContextDeps is created only in _get_eval_session_conversation. Keeping this as one shared object lets future per-call state, such as temporary fields or DB/model changes through the conversation/user references, bleed between eval cases. Move deps = production_agent_deps(service); deps.web_search_enabled = False into run_agent, matching tool_selection.py.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@src/backend/chat/evals/configs/faithfulness_rag.py` around lines 71 - 87,
Move the production_agent_deps(service) call and web_search_enabled assignment
from make_faithfulness_rag_task_fn into run_agent so each eval case receives
fresh per-call dependencies. Keep the agent and tool-stub setup unchanged, and
ensure run_agent passes the newly created deps to agent.run.
| tool_output: | | ||
| { | ||
| "web_search": { | ||
| "0": { | ||
| "url": "https://example.com/gouvernement-france-2026", | ||
| "title": "Gouvernement de la République française", | ||
| "snippets": [ | ||
| "Le premier ministre de la République française est François Bayrou depuis décembre 2024.", | ||
| "Il dirige le gouvernement sous la présidence d'Emmanuel Macron." | ||
| ] | ||
| } | ||
| } | ||
| } |
There was a problem hiding this comment.
📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win
Stub states a stale officeholder.
The rest of the dataset is dated mid-2026, but this snippet asserts Bayrou as PM "depuis décembre 2024". Sébastien Lecornu has served as Prime Minister since September 2025, appointed after the Bayrou government fell to a no-confidence vote, and he was reappointed on 10 October 2025. The span assertion still passes, but feeding an incorrect "current" fact to the agent undercuts the case's intent and will age poorly.
Consider making the snippet timeline-neutral (e.g. a generic "Le premier ministre en exercice est X depuis " placeholder consistent with the 2026 framing) so the fixture doesn't need factual upkeep.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@src/backend/chat/evals/datasets/tool_selection.yaml` around lines 111 - 123,
Update the web_search tool_output fixture in the dataset entry to remove the
stale François Bayrou officeholder claim and use a timeline-neutral placeholder
consistent with the mid-2026 framing. Preserve the existing JSON structure, URL,
title, and span-oriented evaluation behavior while avoiding a specific current
Prime Minister or date that requires future factual updates.
| def evaluate(self, ctx: EvaluatorContext) -> EvaluationReason: | ||
| response_urls = _extract_urls(ctx.output) |
There was a problem hiding this comment.
🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win
Guard ctx.output like the other inputs.
Every other source is isinstance(..., str)-checked, but a non-string ctx.output makes re.findall raise TypeError and turns the case into an evaluator error.
🛡️ Proposed fix
def evaluate(self, ctx: EvaluatorContext) -> EvaluationReason:
- response_urls = _extract_urls(ctx.output)
+ response_urls = _extract_urls(ctx.output) if isinstance(ctx.output, str) else set()📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| def evaluate(self, ctx: EvaluatorContext) -> EvaluationReason: | |
| response_urls = _extract_urls(ctx.output) | |
| def evaluate(self, ctx: EvaluatorContext) -> EvaluationReason: | |
| response_urls = _extract_urls(ctx.output) if isinstance(ctx.output, str) else set() |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@src/backend/chat/evals/evaluators/url_regex.py` around lines 23 - 24, Update
evaluate in the URL regex evaluator to validate ctx.output with an
isinstance(..., str) guard before calling _extract_urls, matching the handling
of other inputs. Ensure non-string output follows the evaluator’s existing
non-match/reasoning path instead of reaching re.findall and raising TypeError.
| def _get_eval_session_user() -> User: | ||
| """Return a single eval user reused for the lifetime of the Python process.""" | ||
| global EVAL_SESSION_USER # pylint: disable=global-statement | ||
| if EVAL_SESSION_USER is not None: | ||
| return EVAL_SESSION_USER | ||
|
|
||
| EVAL_SESSION_USER, _ = User.objects.get_or_create( | ||
| sub=_EVAL_USER_SUB, | ||
| defaults={ | ||
| "email": "eval-session@localhost", | ||
| "full_name": "Eval Session", | ||
| "allow_smart_web_search": True, | ||
| "password": make_password(None), | ||
| }, | ||
| ) | ||
| return EVAL_SESSION_USER | ||
|
|
||
|
|
||
| def _get_eval_session_conversation(user: User) -> chat_models.ChatConversation: | ||
| """Return a single conversation for the eval session user.""" | ||
| global EVAL_SESSION_CONVERSATION # pylint: disable=global-statement | ||
| if EVAL_SESSION_CONVERSATION is not None: | ||
| return EVAL_SESSION_CONVERSATION | ||
|
|
||
| EVAL_SESSION_CONVERSATION, _ = chat_models.ChatConversation.objects.get_or_create( | ||
| owner=user, | ||
| title=_EVAL_CONVERSATION_TITLE, | ||
| ) | ||
| return EVAL_SESSION_CONVERSATION |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟠 Major | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# Inspect how AIAgentService loads/persists conversation history, and whether
# run_evals.py resets it or parallelizes case execution.
rg -nP -C5 '_run_agent|message_history' src/backend/chat/clients/pydantic_ai.py
rg -nP -C5 'asyncio\.gather|ThreadPoolExecutor|async def' src/backend/chat/management/commands/run_evals.py
rg -nP -C3 'ChatMessage\.objects|\.messages\.' src/backend/chat/clients/pydantic_ai.pyRepository: suitenumerique/conversations
Length of output: 4058
🏁 Script executed:
#!/bin/bash
set -u
echo "== file sizes =="
wc -l src/backend/chat/commands/run_evals.py 2>/dev/null || true
wc -l src/backend/chat/management/commands/run_evals.py 2>/dev/null || true
wc -l src/backend/chat/evals/production_agent.py 2>/dev/null || true
echo "== candidate files =="
fd 'run_evals\.py|production_agent\.py|pydantic_ai\.py' src/backend/chat | sed -n '1,80p'
echo "== run_evals relevant sections =="
python3 - <<'PY'
from pathlib import Path
p=Path('src/backend/chat/management/commands/run_evals.py')
if not p.exists():
p=Path('src/backend/chat/commands/run_evals.py')
data=p.read_text()
lines=data.splitlines()
for name, start, end in [('run_async_or_sync',180,290),('run_evals',290,380),('parallel',0,440)]:
print(f'--- {name} {start}-{end} ---')
for i in range(start,end+1):
if 0 < i <= len(lines):
print(f'{i:5}: {lines[i-1]}')
PY
echo "== production_agent relevant sections =="
p=Path('src/backend/chat/evals/production_agent.py')
data=p.read_text()
lines=data.splitlines()
for start,end in [(60,150),(150,240),(240,460)]:
print(f'--- {start}-{end} ---')
for i in range(start,end+1):
if 0 < i <= len(lines):
print(f'{i:5}: {lines[i-1]}')
echo "== pydantic_ai relevant sections =="
p=Path('src/backend/chat/clients/pydantic_ai.py')
data=p.read_text()
lines=data.splitlines()
for start,end in [(1510,1575)]:
print(f'--- {start}-{end} ---')
for i in range(start,end+1):
if 0 < i <= len(lines):
print(f'{i:5}: {lines[i-1]}')
echo "== usages of constants/helpers/models =="
rg -n "_get_eval_session_user|_get_eval_session_conversation|EVAL_SESSION_USER|EVAL_SESSION_CONVERSATION|_EVAL_USER_SUB|_EVAL_CONVERSATION_TITLE|reset_eval_session_cache|ChatMessage.objects|\.messages" src/backend/chat/evals src/backend/chatRepository: suitenumerique/conversations
Length of output: 19148
🏁 Script executed:
#!/bin/bash
set -u
echo "== production_agent relevant sections =="
python3 - <<'PY'
from pathlib import Path
p=Path('src/backend/chat/evals/production_agent.py')
data=p.read_text()
lines=data.splitlines()
for start,end in [(1,199), (150,199)]:
print(f'--- {start}-{end} ---')
for i in range(start,end+1):
print(f'{i:5}: {lines[i-1]}')
PY
echo "== pydantic_ai relevant sections =="
python3 - <<'PY'
from pathlib import Path
p=Path('src/backend/chat/clients/pydantic_ai.py')
data=p.read_text()
lines=data.splitlines()
for start,end in [(280,340),(1510,1620)]:
print(f'--- {start}-{end} ---')
for i in range(start,end+1):
print(f'{i:5}: {lines[i-1]}')
PY
echo "== model definition and message persistence =="
fd 'models\.py' src/backend/chat -x sh -c 'echo "--- $1 ---"; sed -n "1,220p" "$1"' sh {}
echo "== call sites for run prompt with conversation =="
rg -n "def run\(|_get_eval_session_(user|conversation)|MessageHistory|history|messages|iterate_history|ChatMessage" src/backend/chat/agents src/backend/chat/clientsRepository: suitenumerique/conversations
Length of output: 50384
Reset or recreate the eval conversation before each make eval run.
build_production_agent_service() reuses one _EVAL_CONVERSATION_TITLE/_EVAL_USER_SUB _ conversation via get_or_create, and run_evals.py calls _agent.run(prompt) without passing message_history=[]. Since AIAgentService._prepare_agent_run() loads conversation.pydantic_messages, each separate invocation after a completed eval case will start with the previous run’s chat history. Either create a fresh conversation per eval case/run or clear/reset this fixed conversation before running cases.
Also make the module-global creation path safe, e.g. get_or_create() inside a lock/synchronization primitive, because parallel case execution could duplicate the read-then-create check.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@src/backend/chat/evals/production_agent.py` around lines 71 - 99, Reset or
recreate the eval conversation before each eval case/run so
_get_eval_session_conversation does not reuse prior pydantic_messages; update
the surrounding eval setup or run flow while preserving the shared eval user
behavior. Also synchronize the module-global initialization in
_get_eval_session_user and _get_eval_session_conversation, including their
get_or_create paths, so parallel execution cannot duplicate creation.
| def handle(self, *args, **options): | ||
| output_path = generate_dashboard() | ||
| # The command runs inside Docker where BASE_DIR is /app; show the | ||
| # host-side path (repo checkout) so the user can open the file directly. | ||
| display_path = Path("src/backend") / output_path.relative_to(settings.BASE_DIR) | ||
| self.stdout.write( | ||
| self.style.SUCCESS( | ||
| f"Dashboard written to {display_path}. Open it in a browser to compare runs." | ||
| ) | ||
| ) |
There was a problem hiding this comment.
🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win
relative_to can raise for a purely cosmetic path.
If output_path isn't under settings.BASE_DIR (running outside the container, different BASE_DIR), Line 20 raises ValueError after the dashboard was already written successfully. Fall back to the absolute path.
🛡️ Proposed guard
- display_path = Path("src/backend") / output_path.relative_to(settings.BASE_DIR)
+ try:
+ display_path = Path("src/backend") / output_path.relative_to(settings.BASE_DIR)
+ except ValueError:
+ display_path = output_path📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| def handle(self, *args, **options): | |
| output_path = generate_dashboard() | |
| # The command runs inside Docker where BASE_DIR is /app; show the | |
| # host-side path (repo checkout) so the user can open the file directly. | |
| display_path = Path("src/backend") / output_path.relative_to(settings.BASE_DIR) | |
| self.stdout.write( | |
| self.style.SUCCESS( | |
| f"Dashboard written to {display_path}. Open it in a browser to compare runs." | |
| ) | |
| ) | |
| def handle(self, *args, **options): | |
| output_path = generate_dashboard() | |
| # The command runs inside Docker where BASE_DIR is /app; show the | |
| # host-side path (repo checkout) so the user can open the file directly. | |
| try: | |
| display_path = Path("src/backend") / output_path.relative_to(settings.BASE_DIR) | |
| except ValueError: | |
| display_path = output_path | |
| self.stdout.write( | |
| self.style.SUCCESS( | |
| f"Dashboard written to {display_path}. Open it in a browser to compare runs." | |
| ) | |
| ) |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@src/backend/chat/management/commands/generate_eval_dashboard.py` around lines
16 - 25, Update the path formatting in handle around
output_path.relative_to(settings.BASE_DIR) to catch cases where output_path is
outside settings.BASE_DIR, preserving the successful dashboard generation and
falling back to displaying output_path’s absolute path. Keep the existing
host-side display path when relative_to succeeds.
| def _validate_completion(self, response: chat.ChatCompletion) -> _ChatCompletion: | ||
| """Normalize Albert API quirks before validation. | ||
|
|
||
| Albert's OpenAI-compatible API has two known non-conformances: | ||
| 1. tool_calls[].type may not be 'function' — normalized to 'function', | ||
| unless the tool call is a genuine custom tool call (type='custom' | ||
| with a `custom` payload, which the openai SDK requires). | ||
| 2. On multi-turn tool-call conversations, the second response sometimes | ||
| returns a non-standard `object` value and a non-list `choices` field. | ||
| Both are normalized before passing to _ChatCompletion.model_validate(). | ||
| """ | ||
| data = response.model_dump() | ||
|
|
||
| if data.get("object") != "chat.completion": | ||
| data["object"] = "chat.completion" | ||
|
|
||
| if not isinstance(data.get("choices"), list): | ||
| data["choices"] = [] | ||
|
|
||
| for choice in data.get("choices") or []: | ||
| for tool_call in (choice.get("message") or {}).get("tool_calls") or []: | ||
| if not isinstance(tool_call, dict): | ||
| continue | ||
| # 'custom' is only valid with a `custom` payload; any other | ||
| # type (including 'custom' with a function payload) must be | ||
| # 'function' to pass the openai SDK union validation. | ||
| is_custom = tool_call.get("type") == "custom" and "custom" in tool_call | ||
| if not is_custom and tool_call.get("type") != "function": | ||
| tool_call["type"] = "function" | ||
|
|
||
| return _ChatCompletion.model_validate(data) |
There was a problem hiding this comment.
📐 Maintainability & Code Quality | 🟠 Major | ⚡ Quick win
Reduce _validate_completion complexity to unblock SonarCloud.
The quality gate reports cognitive complexity 16 where 15 is allowed. Extract tool-call type normalization into a small helper.
Proposed refactor
+ `@staticmethod`
+ def _normalize_tool_call_type(tool_call: dict[str, Any]) -> None:
+ is_custom = tool_call.get("type") == "custom" and "custom" in tool_call
+ if not is_custom and tool_call.get("type") != "function":
+ tool_call["type"] = "function"
+
def _validate_completion(self, response: chat.ChatCompletion) -> _ChatCompletion:
...
for choice in data.get("choices") or []:
for tool_call in (choice.get("message") or {}).get("tool_calls") or []:
if not isinstance(tool_call, dict):
continue
- is_custom = tool_call.get("type") == "custom" and "custom" in tool_call
- if not is_custom and tool_call.get("type") != "function":
- tool_call["type"] = "function"
+ self._normalize_tool_call_type(tool_call)📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| def _validate_completion(self, response: chat.ChatCompletion) -> _ChatCompletion: | |
| """Normalize Albert API quirks before validation. | |
| Albert's OpenAI-compatible API has two known non-conformances: | |
| 1. tool_calls[].type may not be 'function' — normalized to 'function', | |
| unless the tool call is a genuine custom tool call (type='custom' | |
| with a `custom` payload, which the openai SDK requires). | |
| 2. On multi-turn tool-call conversations, the second response sometimes | |
| returns a non-standard `object` value and a non-list `choices` field. | |
| Both are normalized before passing to _ChatCompletion.model_validate(). | |
| """ | |
| data = response.model_dump() | |
| if data.get("object") != "chat.completion": | |
| data["object"] = "chat.completion" | |
| if not isinstance(data.get("choices"), list): | |
| data["choices"] = [] | |
| for choice in data.get("choices") or []: | |
| for tool_call in (choice.get("message") or {}).get("tool_calls") or []: | |
| if not isinstance(tool_call, dict): | |
| continue | |
| # 'custom' is only valid with a `custom` payload; any other | |
| # type (including 'custom' with a function payload) must be | |
| # 'function' to pass the openai SDK union validation. | |
| is_custom = tool_call.get("type") == "custom" and "custom" in tool_call | |
| if not is_custom and tool_call.get("type") != "function": | |
| tool_call["type"] = "function" | |
| return _ChatCompletion.model_validate(data) | |
| `@staticmethod` | |
| def _normalize_tool_call_type(tool_call: dict[str, Any]) -> None: | |
| is_custom = tool_call.get("type") == "custom" and "custom" in tool_call | |
| if not is_custom and tool_call.get("type") != "function": | |
| tool_call["type"] = "function" | |
| def _validate_completion(self, response: chat.ChatCompletion) -> _ChatCompletion: | |
| """Normalize Albert API quirks before validation. | |
| Albert's OpenAI-compatible API has two known non-conformances: | |
| 1. tool_calls[].type may not be 'function' — normalized to 'function', | |
| unless the tool call is a genuine custom tool call (type='custom' | |
| with a `custom` payload, which the openai SDK requires). | |
| 2. On multi-turn tool-call conversations, the second response sometimes | |
| returns a non-standard `object` value and a non-list `choices` field. | |
| Both are normalized before passing to _ChatCompletion.model_validate(). | |
| """ | |
| data = response.model_dump() | |
| if data.get("object") != "chat.completion": | |
| data["object"] = "chat.completion" | |
| if not isinstance(data.get("choices"), list): | |
| data["choices"] = [] | |
| for choice in data.get("choices") or []: | |
| for tool_call in (choice.get("message") or {}).get("tool_calls") or []: | |
| if not isinstance(tool_call, dict): | |
| continue | |
| self._normalize_tool_call_type(tool_call) | |
| return _ChatCompletion.model_validate(data) |
🧰 Tools
🪛 GitHub Check: SonarCloud Code Analysis
[failure] 78-78: Refactor this function to reduce its Cognitive Complexity from 16 to the 15 allowed.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@src/backend/chat/providers/albert_models.py` around lines 78 - 108, Reduce
cognitive complexity in _validate_completion by extracting the tool-call type
normalization loop and its custom-payload condition into a small helper. Have
_validate_completion invoke the helper for each valid choice while preserving
the existing handling of malformed choices, tool calls, and genuine custom tool
calls.
Source: Linters/SAST tools
| assert len(comparison.coverage_gaps) == 2 | ||
| assert comparison.has_regression_failures | ||
| assert len(comparison.regressions) == 1 |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win
Keep missing cases out of regressions.
A case absent from the after run is a coverage gap, not a case that “fail[s] after.” This assertion locks in double-counting for previously passing cases; represent after_passed as unknown and exclude coverage gaps from regressions.
Proposed test adjustment
assert len(comparison.coverage_gaps) == 2
assert comparison.has_regression_failures
- assert len(comparison.regressions) == 1
+ assert not comparison.regressions📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| assert len(comparison.coverage_gaps) == 2 | |
| assert comparison.has_regression_failures | |
| assert len(comparison.regressions) == 1 | |
| assert len(comparison.coverage_gaps) == 2 | |
| assert comparison.has_regression_failures | |
| assert not comparison.regressions |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@src/backend/chat/tests/evals/test_compare.py` around lines 47 - 49, Update
the comparison fixture and assertions around comparison.coverage_gaps and
comparison.regressions so cases missing from the after run are represented with
after_passed unknown and excluded from regressions. Preserve coverage_gaps for
those cases, and adjust the expected regression count to verify previously
passing missing cases are not double-counted.
| """You are l’Assistant IA, a conversational assistant deployed by the DINUM | ||
| (Direction interministérielle du numérique) for French public servants | ||
| (agents publics), based on Mistral Medium 2508 and hosted on sovereign | ||
| infrastructure. |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win
Hardcoded model identity in the default prompt will be wrong on other deployments.
The served model is resolved from LLM_DEFAULT_MODEL_HRID/LLM_CONFIGURATIONS, so pinning "based on Mistral Medium 2508" into the default instructions makes the assistant state a false identity anywhere else — and it directly conflicts with the "never present an assumption as a fact" rule below. The about_model case in datasets/tool_selection.yaml exercises this path.
📝 Proposed fix
-(Direction interministérielle du numérique) for French public servants
-(agents publics), based on Mistral Medium 2508 and hosted on sovereign
-infrastructure.
+(Direction interministérielle du numérique) for French public servants
+(agents publics), hosted on sovereign infrastructure.Deployment-specific model naming is better sourced from the model configuration (or the self_documentation tool) than from a static default.
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| """You are l’Assistant IA, a conversational assistant deployed by the DINUM | |
| (Direction interministérielle du numérique) for French public servants | |
| (agents publics), based on Mistral Medium 2508 and hosted on sovereign | |
| infrastructure. | |
| """You are l’Assistant IA, a conversational assistant deployed by the DINUM | |
| (Direction interministérielle du numérique) for French public servants | |
| (agents publics), hosted on sovereign infrastructure. |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@src/backend/conversations/settings.py` around lines 745 - 748, Remove the
hardcoded “Mistral Medium 2508” identity from the default prompt in the
assistant instructions, while preserving the deployment-neutral description and
the existing prohibition against presenting assumptions as facts. Ensure
model-specific identity is obtained from the resolved LLM configuration or the
self_documentation tool so the about_model path reflects the active deployment.



Purpose
This PR introduces a behavioral eval framework for
ConversationAgent. Unlike unit tests, these evals exercise LLM behaviour end-to-end:A failing eval means a documented behaviour has regressed. The system is designed to grow: adding a new dataset requires one YAML file, one config file, and a registry entry.
Proposal
Running evals
make eval/make eval-debug— runs evals inside Docker; debug mode exposes debugpy on port 5678 for VS Code remote attachcommit,branch,dirty) is injected from the host viaEVAL_GIT_*env vars (Docker has no.gitmount)run_evalsmanagement command:--dataset,--case,--runs,--verbose,--no-llm-judge,--save,--comment,--include-outputsLLM_DEFAULT_MODEL_HRID; judge =LLM_EVAL_JUDGE_MODEL_HRID(falls back to default; warns when judge == tested model)--saveis rejected when combined with--case(partial runs would register coverage gaps vs baseline)Datasets (4)
url_hallucinationhttp(s)://URLs; only cites URLs from tool output or user messagefaithfulness_ragdocument_search_rag, must not callweb_searchincertitudetool_selectionweb_search,self_documentation,document_search_rag,summarize) or none — includes adversarial French phrasingself_documentationis covered as cases withintool_selection, not as a standalone dataset.RAG/summarize cases use
inputs.requires_documents: true+ a fake document listing. Per-case stub payloads are staged viatool_stub_responses.py(contextvar) so the model must actually call tools.Evaluators & agent wiring
EvalConfigregistry — each dataset declares rubric, evaluators, and optionalmake_task_fn/agent_classUrlRegexEvaluator— deterministic URL check; strips trailing punctuation; reads runtime tool returns viaset_eval_attributeHasNoMatchingSpan— correct "tool was NOT called" check (avoidsnot_(HasMatchingSpan)false positives)production_agent.py— builds agents with production tool wiring; stubs external calls (no DB/API)Persistence, baselines & comparison
--savewrites JSON runs underchat/evals/runs/(gitignored) with git metadata, dataset hashes, per-case avg scores, repeat pass ratesoverall_pass_rateweighted by case count (not per-dataset average)make eval-baseline— promotes a run to committed baseline (baselines/main.json+main_run.json)make eval-compare— CLI diff with regression detection and coverage-gap handling (--fail-on-regression)make eval-reset— wipe local runs/baselines/dashboard (--keep-baselines,--keep-dashboard,--dry-run)HTML dashboard
make eval-dashboard— self-contained HTML fromtemplate.html+ embedded run datacompare.pysemanticsAlbert API compatibility
AlbertOpenAIChatModel._validate_completion— normalizes Albert non-stream responses for pydantic-ai / pydantic_evals:tool_calls[].type→functionobject/ non-listchoiceson multi-turn tool-call conversationsDocs & tests
chat/evals/README.md— structure, datasets, commands, how to add a datasetTest plan
make eval EVAL_ARGS="--dataset url_hallucination"passes on current default modelmake eval EVAL_ARGS="--dataset tool_selection --runs 3"— repeat aggregation looks sanemake eval EVAL_ARGS='--save --comment "test"'thenmake eval-compare— no unexpected regressions vs baselinemake eval-dashboard— opendashboard.html, verify A/B summary and case drill-downmake eval EVAL_ARGS="--no-llm-judge"works when Albert structured output is unavailablebin/pytest chat/tests/evals/greenSummary by CodeRabbit